mirror of
https://github.com/Foundry376/Mailspring.git
synced 2025-10-01 08:54:52 +08:00
fix(system-tray): Move Tray to main process
Summary: - Fixes #1223 - Still uses a package in the renderer process to listen to changes in the unread count and have access to the canvas api. - Renderer process will write icon to disk and inform main process that it should update the icon Test Plan: - Manual Reviewers: evan, bengotow Reviewed By: bengotow Differential Revision: https://phab.nylas.com/D2613
This commit is contained in:
parent
54039db56e
commit
57fb1919ee
7 changed files with 205 additions and 180 deletions
|
@ -1,30 +1,13 @@
|
|||
import SystemTray from './system-tray';
|
||||
import SystemTrayIconStore from './system-tray-icon-store';
|
||||
const platform = process.platform;
|
||||
|
||||
let systemTray;
|
||||
let unsubConfig = ()=>{};
|
||||
|
||||
export function deactivate() {
|
||||
if (systemTray) {
|
||||
systemTray.destroy();
|
||||
systemTray = null;
|
||||
}
|
||||
unsubConfig();
|
||||
export function activate() {
|
||||
this.store = new SystemTrayIconStore(platform);
|
||||
this.store.activate();
|
||||
}
|
||||
|
||||
const onSystemTrayToggle = (showSystemTray)=> {
|
||||
deactivate();
|
||||
if (showSystemTray.newValue) {
|
||||
systemTray = new SystemTray(platform);
|
||||
}
|
||||
};
|
||||
|
||||
export function activate() {
|
||||
deactivate();
|
||||
unsubConfig = NylasEnv.config.onDidChange('core.workspace.systemTray', onSystemTrayToggle).dispose;
|
||||
if (NylasEnv.config.get('core.workspace.systemTray')) {
|
||||
systemTray = new SystemTray(platform);
|
||||
}
|
||||
export function deactivate() {
|
||||
this.store.deactivate();
|
||||
}
|
||||
|
||||
export function serialize() {
|
||||
|
|
87
internal_packages/system-tray/lib/system-tray-icon-store.es6
Normal file
87
internal_packages/system-tray/lib/system-tray-icon-store.es6
Normal file
|
@ -0,0 +1,87 @@
|
|||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import mkdirp from 'mkdirp';
|
||||
import {remote, ipcRenderer} from 'electron';
|
||||
import {UnreadBadgeStore, CanvasUtils} from 'nylas-exports';
|
||||
const {canvasWithSystemTrayIconAndText} = CanvasUtils;
|
||||
const {nativeImage} = remote
|
||||
const mkdirpAsync = Promise.promisify(mkdirp)
|
||||
const writeFile = Promise.promisify(fs.writeFile)
|
||||
|
||||
// Must be absolute real system path
|
||||
// https://github.com/atom/electron/issues/1299
|
||||
const BASE_ICON_PATH = path.join(__dirname, '..', 'assets', process.platform, 'ic-systemtray-nylas.png');
|
||||
const UNREAD_ICON_PATH = path.join(__dirname, '..', 'assets', process.platform, 'ic-systemtray-nylas-unread.png');
|
||||
const TRAY_ICON_PATH = path.join(
|
||||
NylasEnv.getConfigDirPath(),
|
||||
'tray',
|
||||
'tray-icon.png'
|
||||
)
|
||||
|
||||
|
||||
class SystemTrayIconStore {
|
||||
|
||||
constructor(platform) {
|
||||
this._platform = platform;
|
||||
|
||||
this._unreadString = (+UnreadBadgeStore.count()).toLocaleString();
|
||||
this._unreadIcon = nativeImage.createFromPath(UNREAD_ICON_PATH);
|
||||
this._baseIcon = nativeImage.createFromPath(BASE_ICON_PATH);
|
||||
this._icon = this._getIconImg();
|
||||
}
|
||||
|
||||
activate() {
|
||||
const iconDir = path.dirname(TRAY_ICON_PATH)
|
||||
mkdirpAsync(iconDir).then(()=> {
|
||||
writeFile(TRAY_ICON_PATH, this._icon.toPng())
|
||||
.then(()=> {
|
||||
ipcRenderer.send('update-system-tray', TRAY_ICON_PATH, this._unreadString)
|
||||
this._unsubscribe = UnreadBadgeStore.listen(this._onUnreadCountChanged);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
_getIconImg(unreadString = this._unreadString) {
|
||||
const imgHandlers = {
|
||||
'darwin': ()=> {
|
||||
const img = new Image();
|
||||
let canvas = null;
|
||||
|
||||
// toDataUrl always returns the @1x image data, so the assets/darwin/
|
||||
// contains an "@2x" image /without/ the @2x extension
|
||||
img.src = this._baseIcon.toDataURL();
|
||||
|
||||
if (unreadString === '0') {
|
||||
canvas = canvasWithSystemTrayIconAndText(img, '');
|
||||
} else {
|
||||
canvas = canvasWithSystemTrayIconAndText(img, unreadString);
|
||||
}
|
||||
const pngData = nativeImage.createFromDataURL(canvas.toDataURL()).toPng();
|
||||
|
||||
// creating from a buffer allows us to specify that the image is @2x
|
||||
const outputImg = nativeImage.createFromBuffer(pngData);
|
||||
return outputImg;
|
||||
},
|
||||
'default': ()=> {
|
||||
return unreadString !== '0' ? this._unreadIcon : this._baseIcon;
|
||||
},
|
||||
};
|
||||
|
||||
return imgHandlers[this._platform in imgHandlers ? this._platform : 'default']();
|
||||
}
|
||||
|
||||
_onUnreadCountChanged = ()=> {
|
||||
this._unreadString = (+UnreadBadgeStore.count()).toLocaleString();
|
||||
this._icon = this._getIconImg();
|
||||
writeFile(TRAY_ICON_PATH, this._icon.toPng())
|
||||
.then(()=> {
|
||||
ipcRenderer.send('update-system-tray', TRAY_ICON_PATH, this._unreadString)
|
||||
})
|
||||
};
|
||||
|
||||
deactivate() {
|
||||
if (this._unsubscribe) this._unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
export default SystemTrayIconStore;
|
|
@ -1,50 +0,0 @@
|
|||
import {remote, ipcRenderer} from 'electron';
|
||||
import TrayStore from './tray-store';
|
||||
const Tray = remote.require('tray');
|
||||
|
||||
|
||||
class SystemTray {
|
||||
|
||||
constructor(platform) {
|
||||
this._platform = platform;
|
||||
this._store = new TrayStore(this._platform);
|
||||
this._tray = new Tray(this._store.icon());
|
||||
this._tray.setToolTip(this._store.tooltip());
|
||||
|
||||
// Check in case there is no menu for the current platform
|
||||
const menu = this._store.menu();
|
||||
if (menu != null) this._tray.setContextMenu(menu);
|
||||
|
||||
this._unsubscribe = this._addEventListeners();
|
||||
}
|
||||
|
||||
_addEventListeners() {
|
||||
this._tray.addListener('click', this._onClicked.bind(this));
|
||||
const unsubClicked = ()=> this._tray.removeListener('click', this._onClicked);
|
||||
const unsubStore = this._store.listen(this._onChange.bind(this));
|
||||
return ()=> {
|
||||
unsubClicked();
|
||||
unsubStore();
|
||||
};
|
||||
}
|
||||
|
||||
_onClicked() {
|
||||
if (this._platform !== 'darwin') {
|
||||
ipcRenderer.send('command', 'application:show-main-window');
|
||||
}
|
||||
}
|
||||
|
||||
_onChange() {
|
||||
const icon = this._store.icon();
|
||||
const tooltip = this._store.tooltip();
|
||||
this._tray.setImage(icon);
|
||||
this._tray.setToolTip(tooltip);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this._tray.destroy();
|
||||
this._unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
export default SystemTray;
|
|
@ -1,107 +0,0 @@
|
|||
import path from 'path';
|
||||
import NylasStore from 'nylas-store';
|
||||
import {remote, ipcRenderer} from 'electron';
|
||||
import {UnreadBadgeStore, CanvasUtils} from 'nylas-exports';
|
||||
const NativeImage = remote.require('native-image');
|
||||
const Menu = remote.require('menu');
|
||||
const {canvasWithSystemTrayIconAndText} = CanvasUtils;
|
||||
|
||||
// Must be absolute real system path
|
||||
// https://github.com/atom/electron/issues/1299
|
||||
const BASE_ICON_PATH = path.join(__dirname, '..', 'assets', process.platform, 'ic-systemtray-nylas.png');
|
||||
const UNREAD_ICON_PATH = path.join(__dirname, '..', 'assets', process.platform, 'ic-systemtray-nylas-unread.png');
|
||||
|
||||
const _menuTemplate = [
|
||||
{
|
||||
label: 'New Message',
|
||||
click: ()=> ipcRenderer.send('command', 'application:new-message'),
|
||||
},
|
||||
{
|
||||
label: 'Preferences',
|
||||
click: ()=> ipcRenderer.send('command', 'application:open-preferences'),
|
||||
},
|
||||
{
|
||||
type: 'separator',
|
||||
},
|
||||
{
|
||||
label: 'Quit N1',
|
||||
click: ()=> ipcRenderer.send('command', 'application:quit'),
|
||||
},
|
||||
];
|
||||
|
||||
if (process.platform !== 'win32') {
|
||||
_menuTemplate.unshift({
|
||||
label: 'Open Inbox',
|
||||
click: ()=> ipcRenderer.send('command', 'application:show-main-window'),
|
||||
});
|
||||
}
|
||||
|
||||
const _buildMenu = ()=> {
|
||||
return Menu.buildFromTemplate(_menuTemplate);
|
||||
};
|
||||
|
||||
class TrayStore extends NylasStore {
|
||||
|
||||
constructor(platform) {
|
||||
super();
|
||||
this._platform = platform;
|
||||
|
||||
this._unreadIcon = NativeImage.createFromPath(UNREAD_ICON_PATH);
|
||||
this._unreadString = (+UnreadBadgeStore.count()).toLocaleString();
|
||||
this._baseIcon = NativeImage.createFromPath(BASE_ICON_PATH);
|
||||
this._menu = _buildMenu();
|
||||
this._icon = this._getIconImg();
|
||||
|
||||
this.listenTo(UnreadBadgeStore, this._onUnreadCountChanged);
|
||||
}
|
||||
|
||||
icon() {
|
||||
return this._icon;
|
||||
}
|
||||
|
||||
tooltip() {
|
||||
return `${this._unreadString} unread messages`;
|
||||
}
|
||||
|
||||
menu() {
|
||||
return this._menu;
|
||||
}
|
||||
|
||||
_getIconImg() {
|
||||
const imgHandlers = {
|
||||
'darwin': ()=> {
|
||||
const img = new Image();
|
||||
let canvas = null;
|
||||
|
||||
// toDataUrl always returns the @1x image data, so the assets/darwin/
|
||||
// contains an "@2x" image /without/ the @2x extension
|
||||
img.src = this._baseIcon.toDataURL();
|
||||
|
||||
if (this._unreadString === '0') {
|
||||
canvas = canvasWithSystemTrayIconAndText(img, '');
|
||||
} else {
|
||||
canvas = canvasWithSystemTrayIconAndText(img, this._unreadString);
|
||||
}
|
||||
const pngData = NativeImage.createFromDataURL(canvas.toDataURL()).toPng();
|
||||
|
||||
// creating from a buffer allows us to specify that the image is @2x
|
||||
const out2x = NativeImage.createFromBuffer(pngData, 2);
|
||||
out2x.setTemplateImage(true);
|
||||
return out2x;
|
||||
},
|
||||
'default': ()=> {
|
||||
return this._unreadString !== '0' ? this._unreadIcon : this._baseIcon;
|
||||
},
|
||||
};
|
||||
|
||||
return imgHandlers[this._platform in imgHandlers ? this._platform : 'default']();
|
||||
}
|
||||
|
||||
_onUnreadCountChanged() {
|
||||
this._unreadString = (+UnreadBadgeStore.count()).toLocaleString();
|
||||
this._icon = this._getIconImg();
|
||||
this.trigger();
|
||||
}
|
||||
}
|
||||
|
||||
export default TrayStore;
|
|
@ -1,3 +1,4 @@
|
|||
SystemTrayManager = require './system-tray-manager'
|
||||
NylasWindow = require './nylas-window'
|
||||
WindowManager = require './window-manager'
|
||||
ApplicationMenu = require './application-menu'
|
||||
|
@ -56,6 +57,7 @@ class Application
|
|||
nylasProtocolHandler: null
|
||||
resourcePath: null
|
||||
version: null
|
||||
systemTrayManager: null
|
||||
|
||||
exit: (status) -> app.exit(status)
|
||||
|
||||
|
@ -119,6 +121,8 @@ class Application
|
|||
@applicationMenu = new ApplicationMenu(@version)
|
||||
@_databasePhase = 'setup'
|
||||
|
||||
@systemTrayManager = new SystemTrayManager(process.platform, @)
|
||||
|
||||
@listenForArgumentsFromNewProcess()
|
||||
@setupJavaScriptArguments()
|
||||
@handleEvents()
|
||||
|
@ -321,6 +325,7 @@ class Application
|
|||
# Destroy hot windows so that they can't block the app from quitting.
|
||||
# (Electron will wait for them to finish loading before quitting.)
|
||||
@windowManager.unregisterAllHotWindows()
|
||||
@systemTrayManager?.destroy()
|
||||
|
||||
# Called after the app has closed all windows.
|
||||
app.on 'will-quit', =>
|
||||
|
@ -338,6 +343,10 @@ class Application
|
|||
@openUrl(urlToOpen)
|
||||
event.preventDefault()
|
||||
|
||||
# System Tray
|
||||
ipcMain.on 'update-system-tray', (event, iconPath, unreadString) =>
|
||||
@systemTrayManager?.setTrayCount(iconPath, unreadString)
|
||||
|
||||
ipcMain.on 'set-badge-value', (event, value) =>
|
||||
app.dock?.setBadge?(value)
|
||||
|
||||
|
|
102
src/browser/system-tray-manager.es6
Normal file
102
src/browser/system-tray-manager.es6
Normal file
|
@ -0,0 +1,102 @@
|
|||
import fs from 'fs';
|
||||
import {Tray, Menu, nativeImage} from 'electron';
|
||||
|
||||
|
||||
function _getMenuTemplate(platform, application) {
|
||||
const template = [
|
||||
{
|
||||
label: 'New Message',
|
||||
click: ()=> application.emit('application:new-message'),
|
||||
},
|
||||
{
|
||||
label: 'Preferences',
|
||||
click: ()=> application.emit('application:open-preferences'),
|
||||
},
|
||||
{
|
||||
type: 'separator',
|
||||
},
|
||||
{
|
||||
label: 'Quit N1',
|
||||
click: ()=> application.emit('application:quit'),
|
||||
},
|
||||
];
|
||||
|
||||
if (platform !== 'win32') {
|
||||
template.unshift({
|
||||
label: 'Open Inbox',
|
||||
click: ()=> application.emit('application:show-main-window'),
|
||||
});
|
||||
}
|
||||
|
||||
return template;
|
||||
}
|
||||
|
||||
function _getTooltip(unreadString) {
|
||||
return unreadString ? '' : `${unreadString} unread messages`;
|
||||
}
|
||||
|
||||
function _getIcon(iconPath) {
|
||||
if (!iconPath) return nativeImage.createEmpty()
|
||||
try {
|
||||
fs.accessSync(iconPath, fs.F_OK | fs.R_OK)
|
||||
const buffer = fs.readFileSync(iconPath);
|
||||
if (buffer.length > 0) {
|
||||
const out2x = nativeImage.createFromBuffer(buffer, 2);
|
||||
out2x.setTemplateImage(true);
|
||||
return out2x;
|
||||
}
|
||||
return nativeImage.createEmpty();
|
||||
} catch (e) {
|
||||
return nativeImage.createEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class SystemTrayManager {
|
||||
|
||||
constructor(platform, application) {
|
||||
this._platform = platform;
|
||||
this._application = application;
|
||||
this._iconPath = null;
|
||||
this._tray = null;
|
||||
this._initTray();
|
||||
|
||||
this._application.config.onDidChange('core.workspace.systemTray', () => {
|
||||
this.destroy()
|
||||
this._initTray()
|
||||
})
|
||||
}
|
||||
|
||||
_initTray() {
|
||||
if (this._application.config.get('core.workspace.systemTray') !== false) {
|
||||
this._tray = new Tray(_getIcon(this._iconPath));
|
||||
this._tray.setToolTip(_getTooltip());
|
||||
this._tray.setContextMenu(Menu.buildFromTemplate(_getMenuTemplate(this._platform, this._application)));
|
||||
this._unsubscribe = ()=> this._tray.removeListener('click', this._onClick);
|
||||
}
|
||||
}
|
||||
|
||||
_onClick() {
|
||||
if (this._platform !== 'darwin') {
|
||||
this._application.emit('application:show-main-window');
|
||||
}
|
||||
}
|
||||
|
||||
setTrayCount(iconPath, unreadString) {
|
||||
if (!this._tray) return;
|
||||
this._iconPath = iconPath;
|
||||
|
||||
const icon = _getIcon(this._iconPath);
|
||||
const tooltip = _getTooltip(unreadString);
|
||||
this._tray.setImage(icon);
|
||||
this._tray.setToolTip(tooltip);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
if (!this._tray) return;
|
||||
this._unsubscribe();
|
||||
this._tray.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
export default SystemTrayManager;
|
|
@ -54,6 +54,7 @@ module.exports = ErrorLogger = (function() {
|
|||
/////////////////////////////////////////////////////////////////////
|
||||
|
||||
ErrorLogger.prototype.reportError = function(error, extra) {
|
||||
var nslog = require('nslog');
|
||||
if (!error) { error = {stack: ""} }
|
||||
this._appendLog(error.stack)
|
||||
if (extra) { this._appendLog(extra) }
|
||||
|
|
Loading…
Add table
Reference in a new issue