2021-01-07 20:54:00 +01:00
|
|
|
let perfTimestamps = [{ name: 'pre-init', ts: process.hrtime() }];
|
|
|
|
|
2021-02-08 22:49:53 +01:00
|
|
|
if (process.send && process.argv.includes('--native-module-host')) {
|
2021-02-09 20:19:12 +01:00
|
|
|
require('./native-module-host').startInOwnProcess();
|
2021-02-08 22:49:53 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-01-31 07:50:28 +01:00
|
|
|
const electron = require('electron');
|
|
|
|
const path = require('path');
|
|
|
|
const fs = require('fs');
|
2020-06-09 19:26:41 +02:00
|
|
|
const url = require('url');
|
2021-05-08 10:37:24 +02:00
|
|
|
|
|
|
|
const { locale, setLocale, getLocaleValues } = require('./scripts/locale');
|
2021-04-18 14:26:45 +02:00
|
|
|
const { Logger } = require('./scripts/logger');
|
2021-04-21 19:31:37 +02:00
|
|
|
const { isDev } = require('./scripts/util/app-info');
|
2015-10-21 22:32:02 +02:00
|
|
|
|
2020-06-09 19:35:17 +02:00
|
|
|
perfTimestamps?.push({ name: 'loading app requires', ts: process.hrtime() });
|
2020-03-29 10:59:40 +02:00
|
|
|
|
2021-01-07 20:52:52 +01:00
|
|
|
const main = electron.app;
|
2021-04-18 14:26:45 +02:00
|
|
|
const logger = new Logger('remote-app');
|
2019-01-08 18:39:38 +01:00
|
|
|
|
2017-01-31 07:50:28 +01:00
|
|
|
let mainWindow = null;
|
|
|
|
let appIcon = null;
|
|
|
|
let ready = false;
|
|
|
|
let appReady = false;
|
2021-01-08 22:54:45 +01:00
|
|
|
let pendingUpdateFilePath;
|
2017-01-31 07:50:28 +01:00
|
|
|
let mainWindowPosition = {};
|
|
|
|
let updateMainWindowPositionTimeout = null;
|
2020-05-15 22:41:39 +02:00
|
|
|
let mainWindowMaximized = false;
|
2019-01-06 14:45:50 +01:00
|
|
|
|
2020-05-14 20:11:41 +02:00
|
|
|
const windowPositionFileName = 'window-position.json';
|
|
|
|
const portableConfigFileName = 'keeweb-portable.json';
|
|
|
|
|
2020-06-09 19:35:17 +02:00
|
|
|
const startupLogging =
|
2020-06-09 19:36:18 +02:00
|
|
|
process.argv.some((arg) => arg.startsWith('--startup-logging')) ||
|
|
|
|
process.env.KEEWEB_STARTUP_LOGGING === '1';
|
2020-06-09 19:35:17 +02:00
|
|
|
|
2021-01-07 20:52:52 +01:00
|
|
|
const gotTheLock = main.requestSingleInstanceLock();
|
2019-01-06 14:45:50 +01:00
|
|
|
if (!gotTheLock) {
|
2021-01-07 20:52:52 +01:00
|
|
|
main.quit();
|
2019-01-06 14:45:50 +01:00
|
|
|
}
|
|
|
|
|
2020-06-09 19:35:17 +02:00
|
|
|
logProgress('single instance lock');
|
2020-03-29 10:59:40 +02:00
|
|
|
|
2020-06-02 10:47:20 +02:00
|
|
|
let usingPortableUserDataDir = false;
|
2020-05-22 10:56:08 +02:00
|
|
|
let execPath;
|
|
|
|
|
2020-06-02 10:47:20 +02:00
|
|
|
setUserDataPaths();
|
2020-05-14 20:11:41 +02:00
|
|
|
|
2020-06-01 16:53:51 +02:00
|
|
|
let openFile = process.argv.filter((arg) => /\.kdbx$/i.test(arg))[0];
|
2015-10-25 20:26:33 +01:00
|
|
|
|
2020-05-08 16:20:23 +02:00
|
|
|
const htmlPath =
|
2020-06-09 19:26:41 +02:00
|
|
|
(isDev && process.env.KEEWEB_HTML_PATH) ||
|
|
|
|
url.format({ protocol: 'file', slashes: true, pathname: path.join(__dirname, 'index.html') });
|
|
|
|
|
2020-04-15 19:37:03 +02:00
|
|
|
const showDevToolsOnStart =
|
2020-06-01 16:53:51 +02:00
|
|
|
process.argv.some((arg) => arg.startsWith('--devtools')) ||
|
2020-04-15 19:37:03 +02:00
|
|
|
process.env.KEEWEB_OPEN_DEVTOOLS === '1';
|
2015-10-21 22:32:02 +02:00
|
|
|
|
2021-01-07 20:52:52 +01:00
|
|
|
const loginItemSettings = process.platform === 'darwin' ? main.getLoginItemSettings() : {};
|
2020-06-01 15:02:19 +02:00
|
|
|
|
|
|
|
const startMinimized =
|
2020-06-01 16:53:51 +02:00
|
|
|
loginItemSettings.wasOpenedAsHidden ||
|
|
|
|
process.argv.some((arg) => arg.startsWith('--minimized'));
|
2019-10-06 13:14:12 +02:00
|
|
|
|
2019-09-28 13:43:30 +02:00
|
|
|
const themeBgColors = {
|
2020-11-28 23:38:12 +01:00
|
|
|
dark: '#1e1e1e',
|
|
|
|
light: '#f6f6f6',
|
2019-09-28 13:43:30 +02:00
|
|
|
db: '#342f2e',
|
|
|
|
fb: '#282c34',
|
|
|
|
wh: '#fafafa',
|
|
|
|
te: '#222',
|
|
|
|
hc: '#fafafa',
|
|
|
|
sd: '#002b36',
|
2020-11-28 23:38:12 +01:00
|
|
|
sl: '#fdf6e3'
|
2019-09-28 13:43:30 +02:00
|
|
|
};
|
2020-12-19 12:14:32 +01:00
|
|
|
const darkLightThemes = {
|
|
|
|
dark: 'light',
|
2020-12-19 16:39:18 +01:00
|
|
|
sd: 'sl',
|
|
|
|
fb: 'bl',
|
|
|
|
db: 'lb',
|
|
|
|
te: 'lt',
|
|
|
|
dc: 'hc'
|
2020-12-19 12:14:32 +01:00
|
|
|
};
|
2019-09-28 13:43:30 +02:00
|
|
|
const defaultBgColor = '#282C34';
|
|
|
|
|
2020-06-09 19:35:17 +02:00
|
|
|
logProgress('defining args');
|
2017-12-02 20:38:13 +01:00
|
|
|
|
2017-06-05 14:30:22 +02:00
|
|
|
setEnv();
|
2020-05-14 18:55:11 +02:00
|
|
|
setDevAppIcon();
|
2017-06-05 13:33:12 +02:00
|
|
|
|
2020-05-22 20:46:03 +02:00
|
|
|
let configEncryptionKey;
|
|
|
|
let appSettings;
|
|
|
|
|
2020-06-01 16:53:51 +02:00
|
|
|
const settingsPromise = loadSettingsEncryptionKey().then((key) => {
|
2020-05-22 20:46:03 +02:00
|
|
|
configEncryptionKey = key;
|
2020-06-09 19:35:17 +02:00
|
|
|
logProgress('loading settings key');
|
2020-05-22 20:46:03 +02:00
|
|
|
|
2020-06-01 16:53:51 +02:00
|
|
|
return loadConfig('app-settings').then((settings) => {
|
2021-05-09 14:31:58 +02:00
|
|
|
try {
|
|
|
|
appSettings = settings ? JSON.parse(settings) : {};
|
|
|
|
} catch (e) {
|
|
|
|
logStartupMessage(`Error loading app settings: ${e}`);
|
|
|
|
}
|
2020-06-09 19:35:17 +02:00
|
|
|
logProgress('reading app settings');
|
2020-05-22 20:46:03 +02:00
|
|
|
});
|
|
|
|
});
|
2019-09-29 10:34:47 +02:00
|
|
|
|
2021-01-07 20:52:52 +01:00
|
|
|
main.on('window-all-closed', () => {
|
2021-01-08 22:54:45 +01:00
|
|
|
if (pendingUpdateFilePath) {
|
|
|
|
exitAndStartUpdate();
|
2016-07-16 18:39:52 +02:00
|
|
|
} else {
|
2016-04-16 22:34:16 +02:00
|
|
|
if (process.platform !== 'darwin') {
|
2021-01-07 20:52:52 +01:00
|
|
|
main.quit();
|
2016-04-16 22:34:16 +02:00
|
|
|
}
|
2016-07-16 18:39:52 +02:00
|
|
|
}
|
|
|
|
});
|
2021-01-07 20:52:52 +01:00
|
|
|
main.on('ready', () => {
|
2020-06-09 19:35:17 +02:00
|
|
|
logProgress('app on ready');
|
2018-09-02 21:25:42 +02:00
|
|
|
appReady = true;
|
2020-05-22 20:46:03 +02:00
|
|
|
|
|
|
|
settingsPromise
|
|
|
|
.then(() => {
|
|
|
|
createMainWindow();
|
2021-01-10 14:31:33 +01:00
|
|
|
setupIpcHandlers();
|
2020-05-22 20:46:03 +02:00
|
|
|
setGlobalShortcuts(appSettings);
|
|
|
|
subscribePowerEvents();
|
|
|
|
hookRequestHeaders();
|
2021-05-08 10:37:24 +02:00
|
|
|
|
|
|
|
loadLocale().then(() => {
|
|
|
|
setMenu();
|
|
|
|
});
|
2020-05-22 20:46:03 +02:00
|
|
|
})
|
2020-06-01 16:53:51 +02:00
|
|
|
.catch((e) => {
|
2020-05-22 20:46:03 +02:00
|
|
|
electron.dialog.showErrorBox('KeeWeb', 'Error loading app: ' + e);
|
2021-01-09 13:15:25 +01:00
|
|
|
main.exit(2);
|
2020-05-22 20:46:03 +02:00
|
|
|
});
|
2016-07-16 18:39:52 +02:00
|
|
|
});
|
2021-01-07 20:52:52 +01:00
|
|
|
main.on('open-file', (e, path) => {
|
2016-07-16 18:39:52 +02:00
|
|
|
e.preventDefault();
|
|
|
|
openFile = path;
|
|
|
|
notifyOpenFile();
|
|
|
|
});
|
2021-01-07 20:52:52 +01:00
|
|
|
main.on('activate', () => {
|
2016-07-16 18:39:52 +02:00
|
|
|
if (process.platform === 'darwin') {
|
2020-08-08 12:20:08 +02:00
|
|
|
if (appReady && !mainWindow && appSettings) {
|
2016-07-16 18:39:52 +02:00
|
|
|
createMainWindow();
|
2020-11-20 10:29:47 +01:00
|
|
|
} else if (appIcon) {
|
|
|
|
restoreMainWindow();
|
2016-07-16 18:39:52 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
2021-01-07 20:52:52 +01:00
|
|
|
main.on('before-quit', (e) => {
|
|
|
|
if (main.hookBeforeQuitEvent && mainWindow) {
|
2020-06-01 16:59:32 +02:00
|
|
|
e.preventDefault();
|
|
|
|
emitRemoteEvent('launcher-before-quit');
|
2020-06-01 14:42:06 +02:00
|
|
|
}
|
|
|
|
});
|
2021-01-07 20:52:52 +01:00
|
|
|
main.on('will-quit', () => {
|
2016-07-16 18:39:52 +02:00
|
|
|
electron.globalShortcut.unregisterAll();
|
|
|
|
});
|
2021-01-07 20:52:52 +01:00
|
|
|
main.on('second-instance', () => {
|
2019-01-08 18:39:38 +01:00
|
|
|
if (mainWindow) {
|
|
|
|
restoreMainWindow();
|
|
|
|
}
|
|
|
|
});
|
2021-01-07 20:52:52 +01:00
|
|
|
main.on('web-contents-created', (event, contents) => {
|
2021-04-18 14:26:45 +02:00
|
|
|
contents.setWindowOpenHandler((e) => {
|
|
|
|
logger.warn(`Prevented new window: ${e.url}`);
|
|
|
|
emitRemoteEvent('log', `Prevented new window: ${e.url}`);
|
|
|
|
return { action: 'deny' };
|
2020-04-17 21:50:53 +02:00
|
|
|
});
|
2020-04-17 21:54:34 +02:00
|
|
|
contents.on('will-navigate', (e, url) => {
|
|
|
|
if (!url.startsWith('https://beta.keeweb.info/') && !url.startsWith(htmlPath)) {
|
|
|
|
e.preventDefault();
|
2021-04-18 14:26:45 +02:00
|
|
|
logger.warn(`Prevented navigation: ${url}`);
|
2020-04-17 21:54:34 +02:00
|
|
|
}
|
|
|
|
});
|
2020-04-17 21:50:53 +02:00
|
|
|
});
|
2021-01-08 22:54:45 +01:00
|
|
|
main.restartAndUpdate = function (updateFilePath) {
|
|
|
|
pendingUpdateFilePath = updateFilePath;
|
2016-07-16 18:39:52 +02:00
|
|
|
mainWindow.close();
|
2016-07-17 13:30:38 +02:00
|
|
|
setTimeout(() => {
|
2021-01-08 22:54:45 +01:00
|
|
|
pendingUpdateFilePath = undefined;
|
2016-07-16 18:39:52 +02:00
|
|
|
}, 1000);
|
|
|
|
};
|
2021-01-07 20:52:52 +01:00
|
|
|
main.minimizeApp = function (menuItemLabels) {
|
2017-12-04 22:33:40 +01:00
|
|
|
let imagePath;
|
2021-02-22 14:02:42 +01:00
|
|
|
// a workaround to correctly restore focus on windows platform
|
2021-02-23 07:43:01 +01:00
|
|
|
// without this workaround, focus is not restored to the previously focused field
|
2021-02-22 14:02:42 +01:00
|
|
|
if (process.platform === 'win32') {
|
|
|
|
mainWindow.minimize();
|
|
|
|
}
|
tray-min-auto-type-select-fix
The fix for alt-tab behavior when KeeWeb is minimized to the tray
in 3dae878 left a problem when auto-type raises a selection list: the
taskbar button shows, and after a selection is made KeeWeb minimizes
to the taskbar but leaves a tray icon present. The same thing happens
if auto-type is canceled by clicking either the minimize button or the
close button at the top right of the selection window. From this state,
various scenarios lead to having duplicate tray icons.
This commit restores the behavior of 1.6.3 when auto-type raises
a selection list while KeeWeb is minimized to the tray: the selection
window shows, the tray icon stays, and no taskbar button shows.
We used to minimize the window after selection regardless of its
previous state; this worked because we hid the taskbar button and
minimized the window when minimizing to the tray, but that's what caused
the alt-tab problem. Since we now hide when minimizing to the tray,
we have to know whether to minimize or hide after selection.
The simplest way to do that is to keep the old behavior of leaving the
tray icon present when auto-type raises a selection window while KeeWeb
is minimized to the tray. Instead of calling minimize on the main
window, launcher-electron.js now calls app.minimizeThenHideIfInTray
which is defined in desktop/app.js. That routine minimizes KeeWeb (which
returns focus to the previously active window) and then hides the main
window if and only if a tray icon is present. Because we don't want a
tray icon and a taskbar button present at the same time, app.minimizeApp
is also changed to restore the call to mainWindow.setSkipTaskbar(true)
in the non-Darwin path; thus, when auto-type raises a selection window,
there won't be a taskbar button if KeeWeb was minimized to the tray.
If auto-type is canceled by clicking the top right close button while a
selection list is displayed and there is a tray icon, the KeeWeb window
is hidden and the tray icon stays, just as one would expect. This is
the most likely way someone using "Minimize app instead of close" would
choose to dismiss the auto-type selection list.
If auto-type is canceled when a selection list is displayed while there
is a tray icon by clicking the top right minimize button, by using
alt-tab, or by clicking outside the selection window, the KeeWeb window
reverts to its normal display and shows in the alt-tab list, but the
tray icon remains and no taskbar button is shown. This is not ideal;
it could be addressed in another commit if it seems worth doing. This
commit mitigates these scenarios by adding a check to app.minimizeApp
to assure that we never create a second tray icon if one is already
present. This can do no harm and might catch other "corner cases" that
are difficult to foresee. The next time the tray icon is clicked or
the app is minimized to the tray by clicking the top right close button
normal behavior is fully restored.
If I've made no mistakes, the only change to the Darwin path is that it,
too, is subject to the check that a new tray icon is not created if one
already exists. I'm guessing that's OK, but I have no way to test
Darwin.
2018-09-05 05:29:45 +02:00
|
|
|
mainWindow.hide();
|
2017-12-04 22:33:40 +01:00
|
|
|
if (process.platform === 'darwin') {
|
2021-01-07 20:52:52 +01:00
|
|
|
main.dock.hide();
|
2020-11-29 12:17:49 +01:00
|
|
|
imagePath = 'macOS-MenubarTemplate.png';
|
2017-12-04 22:33:40 +01:00
|
|
|
} else {
|
|
|
|
imagePath = 'icon.png';
|
2016-07-16 18:39:52 +02:00
|
|
|
}
|
tray-min-auto-type-select-fix
The fix for alt-tab behavior when KeeWeb is minimized to the tray
in 3dae878 left a problem when auto-type raises a selection list: the
taskbar button shows, and after a selection is made KeeWeb minimizes
to the taskbar but leaves a tray icon present. The same thing happens
if auto-type is canceled by clicking either the minimize button or the
close button at the top right of the selection window. From this state,
various scenarios lead to having duplicate tray icons.
This commit restores the behavior of 1.6.3 when auto-type raises
a selection list while KeeWeb is minimized to the tray: the selection
window shows, the tray icon stays, and no taskbar button shows.
We used to minimize the window after selection regardless of its
previous state; this worked because we hid the taskbar button and
minimized the window when minimizing to the tray, but that's what caused
the alt-tab problem. Since we now hide when minimizing to the tray,
we have to know whether to minimize or hide after selection.
The simplest way to do that is to keep the old behavior of leaving the
tray icon present when auto-type raises a selection window while KeeWeb
is minimized to the tray. Instead of calling minimize on the main
window, launcher-electron.js now calls app.minimizeThenHideIfInTray
which is defined in desktop/app.js. That routine minimizes KeeWeb (which
returns focus to the previously active window) and then hides the main
window if and only if a tray icon is present. Because we don't want a
tray icon and a taskbar button present at the same time, app.minimizeApp
is also changed to restore the call to mainWindow.setSkipTaskbar(true)
in the non-Darwin path; thus, when auto-type raises a selection window,
there won't be a taskbar button if KeeWeb was minimized to the tray.
If auto-type is canceled by clicking the top right close button while a
selection list is displayed and there is a tray icon, the KeeWeb window
is hidden and the tray icon stays, just as one would expect. This is
the most likely way someone using "Minimize app instead of close" would
choose to dismiss the auto-type selection list.
If auto-type is canceled when a selection list is displayed while there
is a tray icon by clicking the top right minimize button, by using
alt-tab, or by clicking outside the selection window, the KeeWeb window
reverts to its normal display and shows in the alt-tab list, but the
tray icon remains and no taskbar button is shown. This is not ideal;
it could be addressed in another commit if it seems worth doing. This
commit mitigates these scenarios by adding a check to app.minimizeApp
to assure that we never create a second tray icon if one is already
present. This can do no harm and might catch other "corner cases" that
are difficult to foresee. The next time the tray icon is clicked or
the app is minimized to the tray by clicking the top right close button
normal behavior is fully restored.
If I've made no mistakes, the only change to the Darwin path is that it,
too, is subject to the check that a new tray icon is not created if one
already exists. I'm guessing that's OK, but I have no way to test
Darwin.
2018-09-05 05:29:45 +02:00
|
|
|
mainWindow.setSkipTaskbar(true);
|
|
|
|
if (!appIcon) {
|
2021-01-09 13:27:28 +01:00
|
|
|
const image = electron.nativeImage.createFromPath(path.join(__dirname, 'img', imagePath));
|
tray-min-auto-type-select-fix
The fix for alt-tab behavior when KeeWeb is minimized to the tray
in 3dae878 left a problem when auto-type raises a selection list: the
taskbar button shows, and after a selection is made KeeWeb minimizes
to the taskbar but leaves a tray icon present. The same thing happens
if auto-type is canceled by clicking either the minimize button or the
close button at the top right of the selection window. From this state,
various scenarios lead to having duplicate tray icons.
This commit restores the behavior of 1.6.3 when auto-type raises
a selection list while KeeWeb is minimized to the tray: the selection
window shows, the tray icon stays, and no taskbar button shows.
We used to minimize the window after selection regardless of its
previous state; this worked because we hid the taskbar button and
minimized the window when minimizing to the tray, but that's what caused
the alt-tab problem. Since we now hide when minimizing to the tray,
we have to know whether to minimize or hide after selection.
The simplest way to do that is to keep the old behavior of leaving the
tray icon present when auto-type raises a selection window while KeeWeb
is minimized to the tray. Instead of calling minimize on the main
window, launcher-electron.js now calls app.minimizeThenHideIfInTray
which is defined in desktop/app.js. That routine minimizes KeeWeb (which
returns focus to the previously active window) and then hides the main
window if and only if a tray icon is present. Because we don't want a
tray icon and a taskbar button present at the same time, app.minimizeApp
is also changed to restore the call to mainWindow.setSkipTaskbar(true)
in the non-Darwin path; thus, when auto-type raises a selection window,
there won't be a taskbar button if KeeWeb was minimized to the tray.
If auto-type is canceled by clicking the top right close button while a
selection list is displayed and there is a tray icon, the KeeWeb window
is hidden and the tray icon stays, just as one would expect. This is
the most likely way someone using "Minimize app instead of close" would
choose to dismiss the auto-type selection list.
If auto-type is canceled when a selection list is displayed while there
is a tray icon by clicking the top right minimize button, by using
alt-tab, or by clicking outside the selection window, the KeeWeb window
reverts to its normal display and shows in the alt-tab list, but the
tray icon remains and no taskbar button is shown. This is not ideal;
it could be addressed in another commit if it seems worth doing. This
commit mitigates these scenarios by adding a check to app.minimizeApp
to assure that we never create a second tray icon if one is already
present. This can do no harm and might catch other "corner cases" that
are difficult to foresee. The next time the tray icon is clicked or
the app is minimized to the tray by clicking the top right close button
normal behavior is fully restored.
If I've made no mistakes, the only change to the Darwin path is that it,
too, is subject to the check that a new tray icon is not created if one
already exists. I'm guessing that's OK, but I have no way to test
Darwin.
2018-09-05 05:29:45 +02:00
|
|
|
appIcon = new electron.Tray(image);
|
2021-05-19 10:33:17 +02:00
|
|
|
if (process.platform !== 'darwin') {
|
|
|
|
appIcon.on('click', restoreMainWindow);
|
|
|
|
}
|
tray-min-auto-type-select-fix
The fix for alt-tab behavior when KeeWeb is minimized to the tray
in 3dae878 left a problem when auto-type raises a selection list: the
taskbar button shows, and after a selection is made KeeWeb minimizes
to the taskbar but leaves a tray icon present. The same thing happens
if auto-type is canceled by clicking either the minimize button or the
close button at the top right of the selection window. From this state,
various scenarios lead to having duplicate tray icons.
This commit restores the behavior of 1.6.3 when auto-type raises
a selection list while KeeWeb is minimized to the tray: the selection
window shows, the tray icon stays, and no taskbar button shows.
We used to minimize the window after selection regardless of its
previous state; this worked because we hid the taskbar button and
minimized the window when minimizing to the tray, but that's what caused
the alt-tab problem. Since we now hide when minimizing to the tray,
we have to know whether to minimize or hide after selection.
The simplest way to do that is to keep the old behavior of leaving the
tray icon present when auto-type raises a selection window while KeeWeb
is minimized to the tray. Instead of calling minimize on the main
window, launcher-electron.js now calls app.minimizeThenHideIfInTray
which is defined in desktop/app.js. That routine minimizes KeeWeb (which
returns focus to the previously active window) and then hides the main
window if and only if a tray icon is present. Because we don't want a
tray icon and a taskbar button present at the same time, app.minimizeApp
is also changed to restore the call to mainWindow.setSkipTaskbar(true)
in the non-Darwin path; thus, when auto-type raises a selection window,
there won't be a taskbar button if KeeWeb was minimized to the tray.
If auto-type is canceled by clicking the top right close button while a
selection list is displayed and there is a tray icon, the KeeWeb window
is hidden and the tray icon stays, just as one would expect. This is
the most likely way someone using "Minimize app instead of close" would
choose to dismiss the auto-type selection list.
If auto-type is canceled when a selection list is displayed while there
is a tray icon by clicking the top right minimize button, by using
alt-tab, or by clicking outside the selection window, the KeeWeb window
reverts to its normal display and shows in the alt-tab list, but the
tray icon remains and no taskbar button is shown. This is not ideal;
it could be addressed in another commit if it seems worth doing. This
commit mitigates these scenarios by adding a check to app.minimizeApp
to assure that we never create a second tray icon if one is already
present. This can do no harm and might catch other "corner cases" that
are difficult to foresee. The next time the tray icon is clicked or
the app is minimized to the tray by clicking the top right close button
normal behavior is fully restored.
If I've made no mistakes, the only change to the Darwin path is that it,
too, is subject to the check that a new tray icon is not created if one
already exists. I'm guessing that's OK, but I have no way to test
Darwin.
2018-09-05 05:29:45 +02:00
|
|
|
const contextMenu = electron.Menu.buildFromTemplate([
|
2019-10-05 08:37:10 +02:00
|
|
|
{ label: menuItemLabels.restore, click: restoreMainWindow },
|
|
|
|
{ label: menuItemLabels.quit, click: closeMainWindow }
|
tray-min-auto-type-select-fix
The fix for alt-tab behavior when KeeWeb is minimized to the tray
in 3dae878 left a problem when auto-type raises a selection list: the
taskbar button shows, and after a selection is made KeeWeb minimizes
to the taskbar but leaves a tray icon present. The same thing happens
if auto-type is canceled by clicking either the minimize button or the
close button at the top right of the selection window. From this state,
various scenarios lead to having duplicate tray icons.
This commit restores the behavior of 1.6.3 when auto-type raises
a selection list while KeeWeb is minimized to the tray: the selection
window shows, the tray icon stays, and no taskbar button shows.
We used to minimize the window after selection regardless of its
previous state; this worked because we hid the taskbar button and
minimized the window when minimizing to the tray, but that's what caused
the alt-tab problem. Since we now hide when minimizing to the tray,
we have to know whether to minimize or hide after selection.
The simplest way to do that is to keep the old behavior of leaving the
tray icon present when auto-type raises a selection window while KeeWeb
is minimized to the tray. Instead of calling minimize on the main
window, launcher-electron.js now calls app.minimizeThenHideIfInTray
which is defined in desktop/app.js. That routine minimizes KeeWeb (which
returns focus to the previously active window) and then hides the main
window if and only if a tray icon is present. Because we don't want a
tray icon and a taskbar button present at the same time, app.minimizeApp
is also changed to restore the call to mainWindow.setSkipTaskbar(true)
in the non-Darwin path; thus, when auto-type raises a selection window,
there won't be a taskbar button if KeeWeb was minimized to the tray.
If auto-type is canceled by clicking the top right close button while a
selection list is displayed and there is a tray icon, the KeeWeb window
is hidden and the tray icon stays, just as one would expect. This is
the most likely way someone using "Minimize app instead of close" would
choose to dismiss the auto-type selection list.
If auto-type is canceled when a selection list is displayed while there
is a tray icon by clicking the top right minimize button, by using
alt-tab, or by clicking outside the selection window, the KeeWeb window
reverts to its normal display and shows in the alt-tab list, but the
tray icon remains and no taskbar button is shown. This is not ideal;
it could be addressed in another commit if it seems worth doing. This
commit mitigates these scenarios by adding a check to app.minimizeApp
to assure that we never create a second tray icon if one is already
present. This can do no harm and might catch other "corner cases" that
are difficult to foresee. The next time the tray icon is clicked or
the app is minimized to the tray by clicking the top right close button
normal behavior is fully restored.
If I've made no mistakes, the only change to the Darwin path is that it,
too, is subject to the check that a new tray icon is not created if one
already exists. I'm guessing that's OK, but I have no way to test
Darwin.
2018-09-05 05:29:45 +02:00
|
|
|
]);
|
|
|
|
appIcon.setContextMenu(contextMenu);
|
|
|
|
appIcon.setToolTip('KeeWeb');
|
|
|
|
}
|
|
|
|
};
|
2021-01-07 20:52:52 +01:00
|
|
|
main.minimizeThenHideIfInTray = function () {
|
tray-min-auto-type-select-fix
The fix for alt-tab behavior when KeeWeb is minimized to the tray
in 3dae878 left a problem when auto-type raises a selection list: the
taskbar button shows, and after a selection is made KeeWeb minimizes
to the taskbar but leaves a tray icon present. The same thing happens
if auto-type is canceled by clicking either the minimize button or the
close button at the top right of the selection window. From this state,
various scenarios lead to having duplicate tray icons.
This commit restores the behavior of 1.6.3 when auto-type raises
a selection list while KeeWeb is minimized to the tray: the selection
window shows, the tray icon stays, and no taskbar button shows.
We used to minimize the window after selection regardless of its
previous state; this worked because we hid the taskbar button and
minimized the window when minimizing to the tray, but that's what caused
the alt-tab problem. Since we now hide when minimizing to the tray,
we have to know whether to minimize or hide after selection.
The simplest way to do that is to keep the old behavior of leaving the
tray icon present when auto-type raises a selection window while KeeWeb
is minimized to the tray. Instead of calling minimize on the main
window, launcher-electron.js now calls app.minimizeThenHideIfInTray
which is defined in desktop/app.js. That routine minimizes KeeWeb (which
returns focus to the previously active window) and then hides the main
window if and only if a tray icon is present. Because we don't want a
tray icon and a taskbar button present at the same time, app.minimizeApp
is also changed to restore the call to mainWindow.setSkipTaskbar(true)
in the non-Darwin path; thus, when auto-type raises a selection window,
there won't be a taskbar button if KeeWeb was minimized to the tray.
If auto-type is canceled by clicking the top right close button while a
selection list is displayed and there is a tray icon, the KeeWeb window
is hidden and the tray icon stays, just as one would expect. This is
the most likely way someone using "Minimize app instead of close" would
choose to dismiss the auto-type selection list.
If auto-type is canceled when a selection list is displayed while there
is a tray icon by clicking the top right minimize button, by using
alt-tab, or by clicking outside the selection window, the KeeWeb window
reverts to its normal display and shows in the alt-tab list, but the
tray icon remains and no taskbar button is shown. This is not ideal;
it could be addressed in another commit if it seems worth doing. This
commit mitigates these scenarios by adding a check to app.minimizeApp
to assure that we never create a second tray icon if one is already
present. This can do no harm and might catch other "corner cases" that
are difficult to foresee. The next time the tray icon is clicked or
the app is minimized to the tray by clicking the top right close button
normal behavior is fully restored.
If I've made no mistakes, the only change to the Darwin path is that it,
too, is subject to the check that a new tray icon is not created if one
already exists. I'm guessing that's OK, but I have no way to test
Darwin.
2018-09-05 05:29:45 +02:00
|
|
|
// This function is called when auto-type has displayed a selection list and a selection was made.
|
|
|
|
// To ensure focus returns to the previous window we must minimize first even if we're going to hide.
|
|
|
|
mainWindow.minimize();
|
|
|
|
if (appIcon) mainWindow.hide();
|
2016-07-16 18:39:52 +02:00
|
|
|
};
|
2021-01-07 20:52:52 +01:00
|
|
|
main.getMainWindow = function () {
|
2016-07-16 18:39:52 +02:00
|
|
|
return mainWindow;
|
|
|
|
};
|
2021-01-07 20:52:52 +01:00
|
|
|
main.setHookBeforeQuitEvent = (hooked) => {
|
|
|
|
main.hookBeforeQuitEvent = !!hooked;
|
2020-06-01 14:42:06 +02:00
|
|
|
};
|
2021-01-07 20:52:52 +01:00
|
|
|
main.setGlobalShortcuts = setGlobalShortcuts;
|
|
|
|
main.showAndFocusMainWindow = showAndFocusMainWindow;
|
|
|
|
main.loadConfig = loadConfig;
|
|
|
|
main.saveConfig = saveConfig;
|
|
|
|
main.getAppMainRoot = getAppMainRoot;
|
|
|
|
main.getAppContentRoot = getAppContentRoot;
|
|
|
|
main.httpRequest = httpRequest;
|
2017-03-28 20:08:14 +02:00
|
|
|
|
2020-06-09 19:35:17 +02:00
|
|
|
function logProgress(name) {
|
|
|
|
perfTimestamps?.push({ name, ts: process.hrtime() });
|
2020-11-19 11:12:36 +01:00
|
|
|
logStartupMessage(name);
|
|
|
|
}
|
|
|
|
|
|
|
|
function logStartupMessage(msg) {
|
2020-06-09 19:35:17 +02:00
|
|
|
if (startupLogging) {
|
|
|
|
// eslint-disable-next-line no-console
|
2020-11-19 11:12:36 +01:00
|
|
|
console.log('[startup]', msg);
|
2020-06-09 19:35:17 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-28 23:38:12 +01:00
|
|
|
function checkSettingsTheme(theme) {
|
|
|
|
// old settings migration
|
|
|
|
if (theme === 'macdark') {
|
|
|
|
return 'dark';
|
|
|
|
}
|
|
|
|
if (theme === 'wh') {
|
|
|
|
return 'light';
|
|
|
|
}
|
|
|
|
return theme;
|
|
|
|
}
|
|
|
|
|
2020-05-22 11:44:49 +02:00
|
|
|
function getDefaultTheme() {
|
2020-11-28 23:38:12 +01:00
|
|
|
return 'dark';
|
2020-05-22 11:44:49 +02:00
|
|
|
}
|
|
|
|
|
2020-12-19 12:14:32 +01:00
|
|
|
function selectDarkOrLightTheme(theme) {
|
|
|
|
const dark = electron.nativeTheme.shouldUseDarkColors;
|
|
|
|
for (const [darkTheme, lightTheme] of Object.entries(darkLightThemes)) {
|
|
|
|
if (darkTheme === theme || lightTheme === theme) {
|
|
|
|
return dark ? darkTheme : lightTheme;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return theme;
|
|
|
|
}
|
|
|
|
|
2019-09-29 10:34:47 +02:00
|
|
|
function createMainWindow() {
|
2020-12-19 12:14:32 +01:00
|
|
|
let theme = checkSettingsTheme(appSettings.theme) || getDefaultTheme();
|
2021-03-05 21:08:09 +01:00
|
|
|
if (appSettings.autoSwitchTheme) {
|
2020-12-19 12:14:32 +01:00
|
|
|
theme = selectDarkOrLightTheme(theme);
|
|
|
|
}
|
2020-05-22 11:44:49 +02:00
|
|
|
const bgColor = themeBgColors[theme] || defaultBgColor;
|
2021-03-03 23:42:41 +01:00
|
|
|
|
|
|
|
const isWindows = process.platform === 'win32';
|
|
|
|
let titlebarStyle = appSettings.titlebarStyle;
|
|
|
|
if (titlebarStyle === 'hidden-inset') {
|
|
|
|
titlebarStyle = 'hiddenInset';
|
|
|
|
}
|
|
|
|
const frameless = isWindows && ['hidden', 'hiddenInset'].includes(titlebarStyle);
|
|
|
|
|
2018-01-15 14:50:48 +01:00
|
|
|
const windowOptions = {
|
2015-12-02 22:41:53 +01:00
|
|
|
show: false,
|
2019-08-16 23:05:39 +02:00
|
|
|
width: 1000,
|
|
|
|
height: 700,
|
|
|
|
minWidth: 700,
|
|
|
|
minHeight: 400,
|
2021-03-03 23:42:41 +01:00
|
|
|
titleBarStyle: titlebarStyle,
|
2021-03-03 22:12:49 +01:00
|
|
|
frame: !frameless,
|
2020-05-22 11:44:49 +02:00
|
|
|
backgroundColor: bgColor,
|
2016-07-24 22:58:21 +02:00
|
|
|
webPreferences: {
|
2020-11-20 20:46:01 +01:00
|
|
|
contextIsolation: false,
|
2019-08-17 15:20:00 +02:00
|
|
|
backgroundThrottling: false,
|
|
|
|
nodeIntegration: true,
|
2020-05-31 14:25:50 +02:00
|
|
|
nodeIntegrationInWorker: true,
|
2020-06-09 19:46:37 +02:00
|
|
|
enableRemoteModule: true,
|
|
|
|
spellcheck: false,
|
|
|
|
v8CacheOptions: 'none'
|
2016-07-24 22:58:21 +02:00
|
|
|
}
|
2018-01-15 14:50:48 +01:00
|
|
|
};
|
|
|
|
if (process.platform !== 'win32') {
|
2021-01-09 13:27:28 +01:00
|
|
|
windowOptions.icon = path.join(__dirname, 'img', 'icon.png');
|
2018-01-15 14:50:48 +01:00
|
|
|
}
|
|
|
|
mainWindow = new electron.BrowserWindow(windowOptions);
|
2020-06-09 19:35:17 +02:00
|
|
|
logProgress('creating main window');
|
2020-03-29 10:59:40 +02:00
|
|
|
|
2016-08-13 16:28:06 +02:00
|
|
|
mainWindow.loadURL(htmlPath);
|
2017-12-03 09:02:27 +01:00
|
|
|
mainWindow.once('ready-to-show', () => {
|
2020-06-09 19:35:17 +02:00
|
|
|
logProgress('main window ready');
|
2019-10-06 13:14:12 +02:00
|
|
|
if (startMinimized) {
|
|
|
|
emitRemoteEvent('launcher-started-minimized');
|
|
|
|
} else {
|
|
|
|
mainWindow.show();
|
|
|
|
}
|
2017-12-03 09:03:56 +01:00
|
|
|
ready = true;
|
|
|
|
notifyOpenFile();
|
2020-06-09 19:35:17 +02:00
|
|
|
logProgress('main window shown');
|
2020-03-29 10:59:40 +02:00
|
|
|
reportStartProfile();
|
2020-05-15 21:54:34 +02:00
|
|
|
|
|
|
|
if (showDevToolsOnStart) {
|
|
|
|
mainWindow.webContents.openDevTools({ mode: 'bottom' });
|
|
|
|
}
|
2015-12-02 22:41:53 +01:00
|
|
|
});
|
2017-01-29 11:53:09 +01:00
|
|
|
mainWindow.webContents.on('context-menu', onContextMenu);
|
2016-02-14 14:17:17 +01:00
|
|
|
mainWindow.on('resize', delaySaveMainWindowPosition);
|
|
|
|
mainWindow.on('move', delaySaveMainWindowPosition);
|
2018-09-23 23:31:29 +02:00
|
|
|
mainWindow.on('restore', coerceMainWindowPositionToConnectedDisplay);
|
2020-05-30 17:46:13 +02:00
|
|
|
mainWindow.on('close', mainWindowClosing);
|
|
|
|
mainWindow.on('closed', mainWindowClosed);
|
2019-02-09 12:11:32 +01:00
|
|
|
mainWindow.on('focus', mainWindowFocus);
|
2016-07-24 23:10:03 +02:00
|
|
|
mainWindow.on('blur', mainWindowBlur);
|
2016-07-17 13:30:38 +02:00
|
|
|
mainWindow.on('closed', () => {
|
2015-12-02 22:41:53 +01:00
|
|
|
mainWindow = null;
|
2016-02-14 14:17:17 +01:00
|
|
|
saveMainWindowPosition();
|
2015-12-02 22:41:53 +01:00
|
|
|
});
|
2016-07-17 13:30:38 +02:00
|
|
|
mainWindow.on('minimize', () => {
|
2019-09-20 20:31:19 +02:00
|
|
|
emitRemoteEvent('launcher-minimize');
|
2015-12-02 22:41:53 +01:00
|
|
|
});
|
2020-05-15 22:41:39 +02:00
|
|
|
mainWindow.on('maximize', () => {
|
|
|
|
mainWindowMaximized = true;
|
2021-03-03 22:12:49 +01:00
|
|
|
emitRemoteEvent('launcher-maximize');
|
2020-05-15 22:41:39 +02:00
|
|
|
});
|
|
|
|
mainWindow.on('unmaximize', () => {
|
|
|
|
mainWindowMaximized = false;
|
2021-03-03 22:12:49 +01:00
|
|
|
emitRemoteEvent('launcher-unmaximize');
|
2020-05-15 22:41:39 +02:00
|
|
|
});
|
2017-03-26 15:24:14 +02:00
|
|
|
mainWindow.on('leave-full-screen', () => {
|
2019-09-20 20:31:19 +02:00
|
|
|
emitRemoteEvent('leave-full-screen');
|
2017-03-26 15:24:14 +02:00
|
|
|
});
|
|
|
|
mainWindow.on('enter-full-screen', () => {
|
2019-09-20 20:31:19 +02:00
|
|
|
emitRemoteEvent('enter-full-screen');
|
2017-03-26 15:24:14 +02:00
|
|
|
});
|
2017-06-02 20:04:57 +02:00
|
|
|
mainWindow.on('session-end', () => {
|
2019-09-20 20:31:19 +02:00
|
|
|
emitRemoteEvent('os-lock');
|
2017-06-02 20:04:57 +02:00
|
|
|
});
|
2020-06-09 19:35:17 +02:00
|
|
|
logProgress('configuring main window');
|
2020-03-29 10:59:40 +02:00
|
|
|
|
2016-02-14 14:17:17 +01:00
|
|
|
restoreMainWindowPosition();
|
2020-06-09 19:35:17 +02:00
|
|
|
logProgress('restoring main window position');
|
2015-12-02 22:41:53 +01:00
|
|
|
}
|
|
|
|
|
2015-11-22 19:18:54 +01:00
|
|
|
function restoreMainWindow() {
|
2021-01-07 20:52:52 +01:00
|
|
|
if (process.platform === 'darwin' && !main.dock.isVisible()) {
|
|
|
|
main.dock.show();
|
2020-06-01 14:42:06 +02:00
|
|
|
}
|
2016-04-15 22:40:55 +02:00
|
|
|
if (mainWindow.isMinimized()) {
|
|
|
|
mainWindow.restore();
|
|
|
|
}
|
2015-11-22 19:18:54 +01:00
|
|
|
mainWindow.setSkipTaskbar(false);
|
2018-08-15 23:59:27 +02:00
|
|
|
mainWindow.show();
|
2018-09-23 23:31:29 +02:00
|
|
|
coerceMainWindowPositionToConnectedDisplay();
|
2017-06-05 14:15:22 +02:00
|
|
|
setTimeout(destroyAppIcon, 0);
|
2015-11-22 19:18:54 +01:00
|
|
|
}
|
|
|
|
|
2020-05-15 22:41:39 +02:00
|
|
|
function showAndFocusMainWindow() {
|
2021-05-12 21:38:54 +02:00
|
|
|
if (appIcon) {
|
|
|
|
restoreMainWindow();
|
|
|
|
}
|
2020-05-15 22:41:39 +02:00
|
|
|
if (mainWindowMaximized) {
|
|
|
|
mainWindow.maximize();
|
|
|
|
} else {
|
|
|
|
mainWindow.show();
|
|
|
|
}
|
|
|
|
mainWindow.focus();
|
2021-01-07 20:52:52 +01:00
|
|
|
if (process.platform === 'darwin' && !main.dock.isVisible()) {
|
|
|
|
main.dock.show();
|
2020-06-01 14:42:06 +02:00
|
|
|
}
|
2020-05-15 22:41:39 +02:00
|
|
|
}
|
|
|
|
|
2015-11-22 19:18:54 +01:00
|
|
|
function closeMainWindow() {
|
2019-09-20 20:31:19 +02:00
|
|
|
emitRemoteEvent('launcher-exit-request');
|
2017-06-05 17:14:41 +02:00
|
|
|
setTimeout(destroyAppIcon, 0);
|
2015-12-02 22:41:53 +01:00
|
|
|
}
|
|
|
|
|
2016-04-15 22:40:55 +02:00
|
|
|
function destroyAppIcon() {
|
|
|
|
if (appIcon) {
|
|
|
|
appIcon.destroy();
|
|
|
|
appIcon = null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-02-14 14:17:17 +01:00
|
|
|
function delaySaveMainWindowPosition() {
|
|
|
|
if (updateMainWindowPositionTimeout) {
|
|
|
|
clearTimeout(updateMainWindowPositionTimeout);
|
|
|
|
}
|
|
|
|
updateMainWindowPositionTimeout = setTimeout(updateMainWindowPosition, 500);
|
|
|
|
}
|
|
|
|
|
|
|
|
function updateMainWindowPositionIfPending() {
|
|
|
|
if (updateMainWindowPositionTimeout) {
|
|
|
|
clearTimeout(updateMainWindowPositionTimeout);
|
|
|
|
updateMainWindowPosition();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function updateMainWindowPosition() {
|
|
|
|
if (!mainWindow) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
updateMainWindowPositionTimeout = null;
|
2017-01-31 07:50:28 +01:00
|
|
|
const bounds = mainWindow.getBounds();
|
2016-02-14 14:17:17 +01:00
|
|
|
if (!mainWindow.isMaximized() && !mainWindow.isMinimized() && !mainWindow.isFullScreen()) {
|
|
|
|
mainWindowPosition.x = bounds.x;
|
|
|
|
mainWindowPosition.y = bounds.y;
|
|
|
|
mainWindowPosition.width = bounds.width;
|
|
|
|
mainWindowPosition.height = bounds.height;
|
|
|
|
}
|
|
|
|
mainWindowPosition.maximized = mainWindow.isMaximized();
|
|
|
|
mainWindowPosition.fullScreen = mainWindow.isFullScreen();
|
|
|
|
mainWindowPosition.changed = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
function saveMainWindowPosition() {
|
|
|
|
if (!mainWindowPosition.changed) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
delete mainWindowPosition.changed;
|
|
|
|
try {
|
2020-05-14 20:11:41 +02:00
|
|
|
fs.writeFileSync(
|
2021-01-07 20:52:52 +01:00
|
|
|
path.join(main.getPath('userData'), windowPositionFileName),
|
2020-05-14 20:11:41 +02:00
|
|
|
JSON.stringify(mainWindowPosition),
|
|
|
|
'utf8'
|
|
|
|
);
|
2016-02-14 14:17:17 +01:00
|
|
|
} catch (e) {}
|
|
|
|
}
|
|
|
|
|
|
|
|
function restoreMainWindowPosition() {
|
2021-01-07 20:52:52 +01:00
|
|
|
const fileName = path.join(main.getPath('userData'), windowPositionFileName);
|
2020-05-14 20:11:41 +02:00
|
|
|
fs.readFile(fileName, 'utf8', (e, data) => {
|
2016-02-14 14:17:17 +01:00
|
|
|
if (data) {
|
2021-05-09 14:31:58 +02:00
|
|
|
try {
|
|
|
|
mainWindowPosition = JSON.parse(data);
|
|
|
|
} catch (e) {
|
|
|
|
logStartupMessage(`Error loading main window position: ${e}`);
|
|
|
|
}
|
2016-02-14 14:24:49 +01:00
|
|
|
if (mainWindow && mainWindowPosition) {
|
2016-02-14 14:17:17 +01:00
|
|
|
if (mainWindowPosition.width && mainWindowPosition.height) {
|
2018-09-23 23:31:29 +02:00
|
|
|
mainWindow.setBounds(mainWindowPosition);
|
|
|
|
coerceMainWindowPositionToConnectedDisplay();
|
2016-02-14 14:17:17 +01:00
|
|
|
}
|
2019-08-16 23:05:39 +02:00
|
|
|
if (mainWindowPosition.maximized) {
|
|
|
|
mainWindow.maximize();
|
2020-05-15 22:41:39 +02:00
|
|
|
mainWindowMaximized = true;
|
2019-08-16 23:05:39 +02:00
|
|
|
}
|
|
|
|
if (mainWindowPosition.fullScreen) {
|
|
|
|
mainWindow.setFullScreen(true);
|
|
|
|
}
|
2016-02-14 14:17:17 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2016-07-24 23:10:03 +02:00
|
|
|
function mainWindowBlur() {
|
2019-09-20 20:31:19 +02:00
|
|
|
emitRemoteEvent('main-window-blur');
|
2016-07-24 23:10:03 +02:00
|
|
|
}
|
|
|
|
|
2019-02-09 12:11:32 +01:00
|
|
|
function mainWindowFocus() {
|
2019-09-20 20:31:19 +02:00
|
|
|
emitRemoteEvent('main-window-focus');
|
2019-02-09 12:11:32 +01:00
|
|
|
}
|
|
|
|
|
2020-05-30 17:46:13 +02:00
|
|
|
function mainWindowClosing() {
|
2020-05-22 10:44:59 +02:00
|
|
|
updateMainWindowPositionIfPending();
|
2020-05-30 17:46:13 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
function mainWindowClosed() {
|
2021-01-07 20:52:52 +01:00
|
|
|
main.removeAllListeners('remote-app-event');
|
2020-05-22 10:44:59 +02:00
|
|
|
}
|
|
|
|
|
2019-09-20 20:31:19 +02:00
|
|
|
function emitRemoteEvent(e, arg) {
|
2016-09-07 18:48:08 +02:00
|
|
|
if (mainWindow && mainWindow.webContents) {
|
2021-01-07 20:52:52 +01:00
|
|
|
main.emit('remote-app-event', {
|
2020-04-11 18:24:23 +02:00
|
|
|
name: e,
|
|
|
|
data: arg
|
|
|
|
});
|
2016-09-07 18:48:08 +02:00
|
|
|
}
|
2015-11-22 19:18:54 +01:00
|
|
|
}
|
|
|
|
|
2015-11-02 18:35:56 +01:00
|
|
|
function setMenu() {
|
|
|
|
if (process.platform === 'darwin') {
|
2019-10-26 22:58:48 +02:00
|
|
|
const name = require('electron').app.name;
|
2017-01-31 07:50:28 +01:00
|
|
|
const template = [
|
2015-11-02 18:35:56 +01:00
|
|
|
{
|
|
|
|
label: name,
|
|
|
|
submenu: [
|
2021-05-08 10:37:24 +02:00
|
|
|
{ role: 'about', label: locale.sysMenuAboutKeeWeb?.replace('{}', 'KeeWeb') },
|
2015-11-02 18:35:56 +01:00
|
|
|
{ type: 'separator' },
|
2021-05-08 10:37:24 +02:00
|
|
|
{ role: 'services', submenu: [], label: locale.sysMenuServices },
|
2015-11-02 18:35:56 +01:00
|
|
|
{ type: 'separator' },
|
2021-05-08 10:37:24 +02:00
|
|
|
{
|
|
|
|
accelerator: 'Command+H',
|
|
|
|
role: 'hide',
|
|
|
|
label: locale.sysMenuHide?.replace('{}', 'KeeWeb')
|
|
|
|
},
|
|
|
|
{
|
|
|
|
accelerator: 'Command+Shift+H',
|
|
|
|
role: 'hideothers',
|
|
|
|
label: locale.sysMenuHideOthers
|
|
|
|
},
|
|
|
|
{ role: 'unhide', label: locale.sysMenuUnhide },
|
2015-11-02 18:35:56 +01:00
|
|
|
{ type: 'separator' },
|
2021-05-08 10:37:24 +02:00
|
|
|
{
|
|
|
|
role: 'quit',
|
|
|
|
accelerator: 'Command+Q',
|
|
|
|
label: locale.sysMenuQuit?.replace('{}', 'KeeWeb')
|
|
|
|
}
|
2015-11-02 18:35:56 +01:00
|
|
|
]
|
|
|
|
},
|
|
|
|
{
|
2021-05-08 10:37:24 +02:00
|
|
|
label: locale.sysMenuEdit || 'Edit',
|
2015-11-02 18:35:56 +01:00
|
|
|
submenu: [
|
2021-05-08 10:37:24 +02:00
|
|
|
{ accelerator: 'CmdOrCtrl+Z', role: 'undo', label: locale.sysMenuUndo },
|
|
|
|
{ accelerator: 'Shift+CmdOrCtrl+Z', role: 'redo', label: locale.sysMenuRedo },
|
2015-11-02 18:35:56 +01:00
|
|
|
{ type: 'separator' },
|
2021-05-08 10:37:24 +02:00
|
|
|
{ accelerator: 'CmdOrCtrl+X', role: 'cut', label: locale.sysMenuCut },
|
|
|
|
{ accelerator: 'CmdOrCtrl+C', role: 'copy', label: locale.sysMenuCopy },
|
|
|
|
{ accelerator: 'CmdOrCtrl+V', role: 'paste', label: locale.sysMenuPaste },
|
|
|
|
{
|
|
|
|
accelerator: 'CmdOrCtrl+A',
|
|
|
|
role: 'selectall',
|
|
|
|
label: locale.sysMenuSelectAll
|
|
|
|
}
|
2015-11-02 18:35:56 +01:00
|
|
|
]
|
2017-01-29 11:29:21 +01:00
|
|
|
},
|
|
|
|
{
|
2021-05-08 10:37:24 +02:00
|
|
|
label: locale.sysMenuWindow || 'Window',
|
2019-08-18 08:05:38 +02:00
|
|
|
submenu: [
|
2021-05-08 10:37:24 +02:00
|
|
|
{ accelerator: 'CmdOrCtrl+M', role: 'minimize', label: locale.sysMenuMinimize },
|
|
|
|
{ accelerator: 'Command+W', role: 'close', label: locale.sysMenuClose }
|
2019-08-18 08:05:38 +02:00
|
|
|
]
|
2015-11-02 18:35:56 +01:00
|
|
|
}
|
|
|
|
];
|
2017-01-31 07:50:28 +01:00
|
|
|
const menu = electron.Menu.buildFromTemplate(template);
|
2016-05-13 14:07:46 +02:00
|
|
|
electron.Menu.setApplicationMenu(menu);
|
2019-08-19 20:10:58 +02:00
|
|
|
} else {
|
|
|
|
mainWindow.setMenuBarVisibility(false);
|
2019-11-04 21:36:14 +01:00
|
|
|
mainWindow.setMenu(null);
|
|
|
|
electron.Menu.setApplicationMenu(null);
|
2015-11-02 18:35:56 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-01-29 11:53:09 +01:00
|
|
|
function onContextMenu(e, props) {
|
|
|
|
if (props.inputFieldType !== 'plainText' || !props.isEditable) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const Menu = electron.Menu;
|
|
|
|
const inputMenu = Menu.buildFromTemplate([
|
2019-08-16 23:05:39 +02:00
|
|
|
{ role: 'undo' },
|
|
|
|
{ role: 'redo' },
|
|
|
|
{ type: 'separator' },
|
|
|
|
{ role: 'cut' },
|
|
|
|
{ role: 'copy' },
|
|
|
|
{ role: 'paste' },
|
|
|
|
{ type: 'separator' },
|
|
|
|
{ role: 'selectall' }
|
2017-01-29 11:53:09 +01:00
|
|
|
]);
|
|
|
|
inputMenu.popup(mainWindow);
|
|
|
|
}
|
|
|
|
|
2015-10-24 21:06:44 +02:00
|
|
|
function notifyOpenFile() {
|
|
|
|
if (ready && openFile && mainWindow) {
|
2019-08-16 23:05:39 +02:00
|
|
|
const openKeyfile = process.argv
|
2020-06-01 16:53:51 +02:00
|
|
|
.filter((arg) => arg.startsWith('--keyfile='))
|
|
|
|
.map((arg) => arg.replace('--keyfile=', ''))[0];
|
2017-12-03 20:31:54 +01:00
|
|
|
const fileInfo = JSON.stringify({ data: openFile, key: openKeyfile });
|
2019-08-16 23:05:39 +02:00
|
|
|
mainWindow.webContents.executeJavaScript(
|
|
|
|
'if (window.launcherOpen) { window.launcherOpen(' +
|
|
|
|
fileInfo +
|
|
|
|
'); } ' +
|
|
|
|
' else { window.launcherOpenedFile=' +
|
|
|
|
fileInfo +
|
|
|
|
'; }'
|
|
|
|
);
|
2015-10-25 08:53:05 +01:00
|
|
|
openFile = null;
|
2015-10-24 21:06:44 +02:00
|
|
|
}
|
|
|
|
}
|
2016-02-28 10:34:14 +01:00
|
|
|
|
2019-10-06 08:48:11 +02:00
|
|
|
function setGlobalShortcuts(appSettings) {
|
2019-09-14 16:36:30 +02:00
|
|
|
const defaultShortcutModifiers = process.platform === 'darwin' ? 'Ctrl+Alt+' : 'Shift+Alt+';
|
|
|
|
const defaultShortcuts = {
|
2019-10-06 08:48:11 +02:00
|
|
|
AutoType: { shortcut: defaultShortcutModifiers + 'T', event: 'auto-type' },
|
2019-09-14 16:36:30 +02:00
|
|
|
CopyPassword: { shortcut: defaultShortcutModifiers + 'C', event: 'copy-password' },
|
|
|
|
CopyUser: { shortcut: defaultShortcutModifiers + 'B', event: 'copy-user' },
|
|
|
|
CopyUrl: { shortcut: defaultShortcutModifiers + 'U', event: 'copy-url' },
|
2019-10-06 12:27:18 +02:00
|
|
|
CopyOtp: { event: 'copy-otp' },
|
|
|
|
RestoreApp: { action: restoreMainWindow }
|
2016-02-28 18:13:23 +01:00
|
|
|
};
|
2019-09-14 16:36:30 +02:00
|
|
|
electron.globalShortcut.unregisterAll();
|
|
|
|
for (const [key, shortcutDef] of Object.entries(defaultShortcuts)) {
|
|
|
|
const fromSettings = appSettings[`globalShortcut${key}`];
|
|
|
|
const shortcut = fromSettings || shortcutDef.shortcut;
|
2019-10-06 08:48:11 +02:00
|
|
|
if (shortcut) {
|
|
|
|
try {
|
|
|
|
electron.globalShortcut.register(shortcut, () => {
|
2019-10-06 12:27:18 +02:00
|
|
|
if (shortcutDef.event) {
|
|
|
|
emitRemoteEvent(shortcutDef.event);
|
|
|
|
}
|
|
|
|
if (shortcutDef.action) {
|
|
|
|
shortcutDef.action();
|
|
|
|
}
|
2019-10-06 08:48:11 +02:00
|
|
|
});
|
|
|
|
} catch (e) {}
|
|
|
|
}
|
2019-09-14 16:36:30 +02:00
|
|
|
}
|
2020-06-09 19:35:17 +02:00
|
|
|
logProgress('setting global shortcuts');
|
2016-02-28 10:34:14 +01:00
|
|
|
}
|
2016-08-20 09:04:30 +02:00
|
|
|
|
|
|
|
function subscribePowerEvents() {
|
|
|
|
electron.powerMonitor.on('suspend', () => {
|
2019-09-20 20:31:19 +02:00
|
|
|
emitRemoteEvent('power-monitor-suspend');
|
2016-08-20 09:04:30 +02:00
|
|
|
});
|
|
|
|
electron.powerMonitor.on('resume', () => {
|
2019-09-20 20:31:19 +02:00
|
|
|
emitRemoteEvent('power-monitor-resume');
|
2016-08-20 09:04:30 +02:00
|
|
|
});
|
2019-11-06 23:01:05 +01:00
|
|
|
electron.powerMonitor.on('lock-screen', () => {
|
|
|
|
emitRemoteEvent('os-lock');
|
|
|
|
});
|
2020-06-09 19:35:17 +02:00
|
|
|
logProgress('subscribing to power events');
|
2016-08-20 09:04:30 +02:00
|
|
|
}
|
2017-06-05 13:33:12 +02:00
|
|
|
|
2020-06-02 10:47:20 +02:00
|
|
|
function setUserDataPaths() {
|
2020-05-22 10:56:08 +02:00
|
|
|
execPath = process.execPath;
|
|
|
|
|
2020-06-02 10:47:20 +02:00
|
|
|
let isPortable = false;
|
|
|
|
|
2020-05-14 20:11:41 +02:00
|
|
|
switch (process.platform) {
|
|
|
|
case 'darwin':
|
2020-06-02 10:47:20 +02:00
|
|
|
isPortable = !execPath.includes('/Applications/');
|
2020-05-14 20:11:41 +02:00
|
|
|
if (isPortable) {
|
|
|
|
execPath = execPath.substring(0, execPath.indexOf('.app'));
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 'win32':
|
|
|
|
isPortable = !execPath.includes('Program Files');
|
|
|
|
break;
|
|
|
|
case 'linux':
|
|
|
|
isPortable = !execPath.startsWith('/usr/') && !execPath.startsWith('/opt/');
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2020-05-22 20:46:03 +02:00
|
|
|
if (isDev && process.env.KEEWEB_IS_PORTABLE) {
|
2021-05-09 14:31:58 +02:00
|
|
|
try {
|
|
|
|
isPortable = !!JSON.parse(process.env.KEEWEB_IS_PORTABLE);
|
|
|
|
} catch {}
|
2020-05-22 20:46:03 +02:00
|
|
|
}
|
|
|
|
|
2020-06-09 19:35:17 +02:00
|
|
|
logProgress('portable check');
|
2020-05-22 10:56:08 +02:00
|
|
|
|
2020-05-14 20:11:41 +02:00
|
|
|
if (isPortable) {
|
|
|
|
const portableConfigDir = path.dirname(execPath);
|
|
|
|
const portableConfigPath = path.join(portableConfigDir, portableConfigFileName);
|
|
|
|
|
|
|
|
if (fs.existsSync(portableConfigPath)) {
|
2021-05-09 14:31:58 +02:00
|
|
|
try {
|
|
|
|
const portableConfig = JSON.parse(fs.readFileSync(portableConfigPath, 'utf8'));
|
|
|
|
const portableUserDataDir = path.resolve(
|
|
|
|
portableConfigDir,
|
|
|
|
portableConfig.userDataDir
|
|
|
|
);
|
2020-05-14 20:11:41 +02:00
|
|
|
|
2021-05-09 14:31:58 +02:00
|
|
|
if (!fs.existsSync(portableUserDataDir)) {
|
|
|
|
fs.mkdirSync(portableUserDataDir, { recursive: true });
|
|
|
|
}
|
2020-05-14 20:11:41 +02:00
|
|
|
|
2021-05-09 14:31:58 +02:00
|
|
|
main.setPath('userData', portableUserDataDir);
|
|
|
|
usingPortableUserDataDir = true;
|
|
|
|
} catch (e) {
|
|
|
|
logStartupMessage(`Error loading portable config: ${e}`);
|
|
|
|
}
|
2020-05-14 20:11:41 +02:00
|
|
|
}
|
2020-05-14 18:55:11 +02:00
|
|
|
}
|
|
|
|
|
2020-06-09 19:35:17 +02:00
|
|
|
logProgress('userdata dir');
|
2020-05-14 20:11:41 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
function setEnv() {
|
2019-08-18 08:05:38 +02:00
|
|
|
if (
|
|
|
|
process.platform === 'linux' &&
|
|
|
|
['Pantheon', 'Unity:Unity7'].indexOf(process.env.XDG_CURRENT_DESKTOP) !== -1
|
|
|
|
) {
|
2017-06-05 14:30:22 +02:00
|
|
|
// https://github.com/electron/electron/issues/9046
|
|
|
|
process.env.XDG_CURRENT_DESKTOP = 'Unity';
|
|
|
|
}
|
2020-05-12 23:10:26 +02:00
|
|
|
|
2021-01-07 20:52:52 +01:00
|
|
|
main.commandLine.appendSwitch('disable-background-timer-throttling');
|
2020-05-12 23:46:19 +02:00
|
|
|
|
2020-05-12 23:10:26 +02:00
|
|
|
// disable all caching, since we're not using old profile data anyway
|
2021-01-07 20:52:52 +01:00
|
|
|
main.commandLine.appendSwitch('disable-http-cache');
|
|
|
|
main.commandLine.appendSwitch('disable-gpu-shader-disk-cache');
|
2020-05-12 23:10:26 +02:00
|
|
|
|
2020-10-28 20:18:27 +01:00
|
|
|
if (process.platform === 'linux') {
|
|
|
|
// fixes colors on Linux, see #1621
|
2021-01-07 20:52:52 +01:00
|
|
|
main.commandLine.appendSwitch('force-color-profile', 'srgb');
|
2020-10-28 20:18:27 +01:00
|
|
|
}
|
2020-10-28 20:17:13 +01:00
|
|
|
|
2021-01-07 20:52:52 +01:00
|
|
|
main.allowRendererProcessReuse = true;
|
2020-05-13 17:44:33 +02:00
|
|
|
|
2020-06-09 19:35:17 +02:00
|
|
|
logProgress('setting env');
|
2017-06-05 14:30:22 +02:00
|
|
|
}
|
|
|
|
|
2020-05-10 09:49:09 +02:00
|
|
|
function setDevAppIcon() {
|
2020-05-22 18:47:26 +02:00
|
|
|
if (isDev && htmlPath && process.platform === 'darwin') {
|
2020-05-10 09:49:09 +02:00
|
|
|
const icon = electron.nativeImage.createFromPath(
|
|
|
|
path.join(__dirname, '../graphics/512x512.png')
|
|
|
|
);
|
2021-01-07 20:52:52 +01:00
|
|
|
main.dock.setIcon(icon);
|
2020-05-10 09:49:09 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-08-28 09:49:45 +02:00
|
|
|
// When sending a PUT XMLHttpRequest Chromium includes the header "Origin: file://".
|
|
|
|
// This confuses some WebDAV clients, notably OwnCloud.
|
|
|
|
// The header is invalid, so removing it everywhere it occurs should do no harm.
|
|
|
|
|
|
|
|
function hookRequestHeaders() {
|
|
|
|
electron.session.defaultSession.webRequest.onBeforeSendHeaders((details, callback) => {
|
2020-05-22 20:46:03 +02:00
|
|
|
if (
|
|
|
|
!details.url.startsWith('ws:') &&
|
|
|
|
!details.url.startsWith('https://plugins.keeweb.info/')
|
|
|
|
) {
|
2019-09-08 09:39:30 +02:00
|
|
|
delete details.requestHeaders.Origin;
|
2019-01-07 20:38:58 +01:00
|
|
|
}
|
2020-03-17 07:52:34 +01:00
|
|
|
callback({ requestHeaders: details.requestHeaders });
|
2018-08-28 09:49:45 +02:00
|
|
|
});
|
2020-06-09 19:35:17 +02:00
|
|
|
logProgress('setting request handlers');
|
2018-08-28 09:49:45 +02:00
|
|
|
}
|
2018-09-23 23:31:29 +02:00
|
|
|
|
|
|
|
// If a display is disconnected while KeeWeb is minimized, Electron does not
|
|
|
|
// ensure that the restored window appears on a display that is still connected.
|
|
|
|
// This checks to be sure the title bar is somewhere the user can grab it,
|
|
|
|
// without making it impossible to minimize and restore a window keeping it
|
|
|
|
// partially off-screen or straddling two displays if the user desires that.
|
|
|
|
|
|
|
|
function coerceMainWindowPositionToConnectedDisplay() {
|
2019-01-07 22:04:39 +01:00
|
|
|
const eScreen = electron.screen;
|
2018-09-23 23:31:29 +02:00
|
|
|
const displays = eScreen.getAllDisplays();
|
|
|
|
if (!displays || !displays.length) return;
|
|
|
|
const windowBounds = mainWindow.getBounds();
|
|
|
|
const contentBounds = mainWindow.getContentBounds();
|
|
|
|
const tbLeft = windowBounds.x;
|
|
|
|
const tbRight = windowBounds.x + windowBounds.width;
|
|
|
|
const tbTop = windowBounds.y;
|
|
|
|
const tbBottom = contentBounds.y;
|
|
|
|
// 160px width and 2/3s the title bar height should be enough that the user can grab it
|
|
|
|
for (let i = 0; i < displays.length; ++i) {
|
|
|
|
const workArea = displays[i].workArea;
|
2019-08-18 08:05:38 +02:00
|
|
|
const overlapWidth =
|
|
|
|
Math.min(tbRight, workArea.x + workArea.width) - Math.max(tbLeft, workArea.x);
|
|
|
|
const overlapHeight =
|
|
|
|
Math.min(tbBottom, workArea.y + workArea.height) - Math.max(tbTop, workArea.y);
|
2018-09-23 23:31:29 +02:00
|
|
|
if (overlapWidth >= 160 && 3 * overlapHeight >= 2 * (tbBottom - tbTop)) return;
|
|
|
|
}
|
|
|
|
// If we get here, no display contains a big enough strip of the title bar
|
|
|
|
// that we can be confident the user can drag it into visibility. Rather than
|
|
|
|
// attempt to guess what the user wants, just center it on the primary display.
|
|
|
|
// Try to keep the previous height and width, but clamp each to 90% of the workarea.
|
|
|
|
const workArea = eScreen.getPrimaryDisplay().workArea;
|
|
|
|
const newWidth = Math.min(windowBounds.width, Math.floor(0.9 * workArea.width));
|
|
|
|
const newHeight = Math.min(windowBounds.height, Math.floor(0.9 * workArea.height));
|
|
|
|
mainWindow.setBounds({
|
|
|
|
'x': workArea.x + Math.floor((workArea.width - newWidth) / 2),
|
|
|
|
'y': workArea.y + Math.floor((workArea.height - newHeight) / 2),
|
|
|
|
'width': newWidth,
|
|
|
|
'height': newHeight
|
|
|
|
});
|
|
|
|
updateMainWindowPosition();
|
|
|
|
}
|
2020-03-29 10:59:40 +02:00
|
|
|
|
|
|
|
function reportStartProfile() {
|
2020-04-04 19:46:19 +02:00
|
|
|
if (!perfTimestamps) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-03-29 10:59:40 +02:00
|
|
|
const processCreationTime = process.getCreationTime();
|
|
|
|
const totalTime = Math.round(Date.now() - processCreationTime);
|
|
|
|
let lastTs = 0;
|
|
|
|
const timings = perfTimestamps
|
2020-06-01 16:53:51 +02:00
|
|
|
.map((milestone) => {
|
2020-03-29 10:59:40 +02:00
|
|
|
const ts = milestone.ts;
|
|
|
|
const elapsed = lastTs
|
|
|
|
? Math.round((ts[0] - lastTs[0]) * 1e3 + (ts[1] - lastTs[1]) / 1e6)
|
|
|
|
: 0;
|
|
|
|
lastTs = ts;
|
|
|
|
return {
|
|
|
|
name: milestone.name,
|
|
|
|
elapsed
|
|
|
|
};
|
|
|
|
})
|
|
|
|
.slice(1);
|
|
|
|
|
2021-01-07 20:54:00 +01:00
|
|
|
perfTimestamps = undefined;
|
2020-03-29 10:59:40 +02:00
|
|
|
|
|
|
|
const startProfile = { totalTime, timings };
|
|
|
|
emitRemoteEvent('start-profile', startProfile);
|
|
|
|
}
|
2020-05-22 20:46:03 +02:00
|
|
|
|
2020-06-06 13:30:35 +02:00
|
|
|
function getAppMainRoot() {
|
2020-06-02 13:58:25 +02:00
|
|
|
if (isDev) {
|
2020-06-06 13:30:35 +02:00
|
|
|
return __dirname;
|
2020-06-02 13:58:25 +02:00
|
|
|
} else {
|
2020-06-06 13:30:35 +02:00
|
|
|
return process.mainModule.path;
|
2020-05-22 20:46:03 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-06 13:30:35 +02:00
|
|
|
function getAppContentRoot() {
|
|
|
|
return __dirname;
|
|
|
|
}
|
2020-05-22 20:46:03 +02:00
|
|
|
|
2020-06-06 13:30:35 +02:00
|
|
|
function reqNative(mod) {
|
|
|
|
const fileName = `${mod}-${process.platform}-${process.arch}.node`;
|
2020-05-22 20:46:03 +02:00
|
|
|
|
2020-06-06 13:30:35 +02:00
|
|
|
const modulePath = `../node_modules/@keeweb/keeweb-native-modules/${fileName}`;
|
|
|
|
const fullPath = path.join(getAppMainRoot(), modulePath);
|
2020-05-22 20:46:03 +02:00
|
|
|
|
2020-06-06 13:30:35 +02:00
|
|
|
return require(fullPath);
|
2020-05-22 20:46:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
function loadSettingsEncryptionKey() {
|
|
|
|
return Promise.resolve().then(() => {
|
2020-06-02 10:47:20 +02:00
|
|
|
if (usingPortableUserDataDir) {
|
2020-05-22 20:46:03 +02:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
const keytar = reqNative('keytar');
|
2020-05-31 13:00:35 +02:00
|
|
|
|
2020-06-01 16:53:51 +02:00
|
|
|
return keytar.getPassword('KeeWeb', 'settings-key').then((key) => {
|
2020-05-31 13:00:35 +02:00
|
|
|
if (key) {
|
|
|
|
return Buffer.from(key, 'hex');
|
|
|
|
}
|
|
|
|
key = require('crypto').randomBytes(48);
|
|
|
|
return keytar.setPassword('KeeWeb', 'settings-key', key.toString('hex')).then(() => {
|
|
|
|
return migrateOldConfigs(key).then(() => key);
|
2020-05-22 20:46:03 +02:00
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function loadConfig(name) {
|
|
|
|
const ext = configEncryptionKey ? 'dat' : 'json';
|
2021-01-07 20:52:52 +01:00
|
|
|
const configFilePath = path.join(main.getPath('userData'), `${name}.${ext}`);
|
2020-05-22 20:46:03 +02:00
|
|
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
fs.readFile(configFilePath, (err, data) => {
|
|
|
|
if (err) {
|
|
|
|
if (err.code === 'ENOENT') {
|
|
|
|
resolve(null);
|
|
|
|
} else {
|
|
|
|
reject(`Error reading config ${name}: ${err}`);
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
if (configEncryptionKey) {
|
|
|
|
const key = configEncryptionKey.slice(0, 32);
|
|
|
|
const iv = configEncryptionKey.slice(32, 48);
|
|
|
|
|
|
|
|
const crypto = require('crypto');
|
|
|
|
const cipher = crypto.createDecipheriv('aes-256-cbc', key, iv);
|
|
|
|
|
2020-05-22 22:17:36 +02:00
|
|
|
data = Buffer.concat([cipher.update(data), cipher.final()]);
|
2020-05-22 20:46:03 +02:00
|
|
|
}
|
|
|
|
|
2020-05-22 22:17:36 +02:00
|
|
|
resolve(data.toString('utf8'));
|
2020-05-22 20:46:03 +02:00
|
|
|
} catch (err) {
|
2020-11-19 11:12:36 +01:00
|
|
|
logStartupMessage(`Error reading config data (config ignored) ${name}: ${err}`);
|
|
|
|
resolve(null);
|
2020-05-22 20:46:03 +02:00
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function saveConfig(name, data, key) {
|
|
|
|
if (!key) {
|
|
|
|
key = configEncryptionKey;
|
|
|
|
}
|
|
|
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
try {
|
|
|
|
data = Buffer.from(data);
|
|
|
|
|
2020-05-22 20:55:05 +02:00
|
|
|
if (key) {
|
2020-05-22 20:54:47 +02:00
|
|
|
const crypto = require('crypto');
|
|
|
|
const cipher = crypto.createCipheriv(
|
|
|
|
'aes-256-cbc',
|
|
|
|
key.slice(0, 32),
|
|
|
|
key.slice(32, 48)
|
|
|
|
);
|
|
|
|
|
|
|
|
data = Buffer.concat([cipher.update(data), cipher.final()]);
|
|
|
|
}
|
2020-05-22 20:46:03 +02:00
|
|
|
} catch (err) {
|
|
|
|
return reject(`Error writing config data ${name}: ${err}`);
|
|
|
|
}
|
|
|
|
|
2020-05-24 13:46:03 +02:00
|
|
|
const ext = key ? 'dat' : 'json';
|
2021-01-07 20:52:52 +01:00
|
|
|
const configFilePath = path.join(main.getPath('userData'), `${name}.${ext}`);
|
2020-06-01 16:53:51 +02:00
|
|
|
fs.writeFile(configFilePath, data, (err) => {
|
2020-05-22 20:46:03 +02:00
|
|
|
if (err) {
|
|
|
|
reject(`Error writing config ${name}: ${err}`);
|
|
|
|
} else {
|
|
|
|
resolve();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2021-05-08 10:37:24 +02:00
|
|
|
function loadLocale() {
|
|
|
|
return loadConfig('locale').then((localeValues) => {
|
|
|
|
if (localeValues) {
|
|
|
|
try {
|
|
|
|
localeValues = JSON.parse(localeValues);
|
|
|
|
if (appSettings?.locale === localeValues?.locale) {
|
|
|
|
setLocale(localeValues);
|
|
|
|
}
|
2021-05-09 14:31:58 +02:00
|
|
|
} catch (e) {
|
|
|
|
logStartupMessage(`Error loading locale: ${e}`);
|
|
|
|
}
|
2021-05-08 10:37:24 +02:00
|
|
|
}
|
|
|
|
locale.on('changed', () => {
|
|
|
|
setMenu();
|
|
|
|
saveConfig('locale', JSON.stringify(getLocaleValues()));
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2020-05-22 20:46:03 +02:00
|
|
|
// TODO: delete in 2021
|
|
|
|
function migrateOldConfigs(key) {
|
|
|
|
const knownConfigs = [
|
|
|
|
'file-info',
|
|
|
|
'app-settings',
|
|
|
|
'runtime-data',
|
|
|
|
'update-info',
|
|
|
|
'plugin-gallery',
|
|
|
|
'plugins'
|
|
|
|
];
|
|
|
|
|
|
|
|
const promises = [];
|
|
|
|
|
|
|
|
for (const configName of knownConfigs) {
|
|
|
|
promises.push(
|
2020-06-01 16:53:51 +02:00
|
|
|
loadConfig(configName).then((data) => {
|
2020-05-22 20:46:03 +02:00
|
|
|
if (data) {
|
|
|
|
return saveConfig(configName, data, key).then(() => {
|
2021-01-07 20:52:52 +01:00
|
|
|
fs.unlinkSync(path.join(main.getPath('userData'), `${configName}.json`));
|
2020-05-22 20:46:03 +02:00
|
|
|
});
|
|
|
|
}
|
|
|
|
})
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return Promise.all(promises);
|
|
|
|
}
|
2020-06-11 19:48:30 +02:00
|
|
|
|
|
|
|
function httpRequest(config, log, onLoad) {
|
|
|
|
// eslint-disable-next-line node/no-deprecated-api
|
|
|
|
const opts = url.parse(config.url);
|
|
|
|
|
|
|
|
opts.method = config.method || 'GET';
|
|
|
|
opts.headers = {
|
|
|
|
'User-Agent': mainWindow.webContents.userAgent,
|
|
|
|
...config.headers
|
|
|
|
};
|
|
|
|
opts.timeout = 60000;
|
|
|
|
|
|
|
|
let data;
|
|
|
|
if (config.data) {
|
|
|
|
if (config.dataIsMultipart) {
|
|
|
|
data = Buffer.concat(config.data.map((chunk) => Buffer.from(chunk)));
|
|
|
|
} else {
|
|
|
|
data = Buffer.from(config.data);
|
|
|
|
}
|
|
|
|
// Electron's API doesn't like that, while node.js needs it
|
|
|
|
// opts.headers['Content-Length'] = data.byteLength;
|
|
|
|
}
|
|
|
|
|
|
|
|
const req = electron.net.request(opts);
|
|
|
|
|
|
|
|
req.on('response', (res) => {
|
|
|
|
const chunks = [];
|
|
|
|
const onClose = () => {
|
|
|
|
log('info', 'HTTP response', opts.method, config.url, res.statusCode, res.headers);
|
|
|
|
onLoad({
|
|
|
|
status: res.statusCode,
|
|
|
|
response: Buffer.concat(chunks).toString('hex'),
|
|
|
|
headers: res.headers
|
|
|
|
});
|
|
|
|
};
|
|
|
|
res.on('data', (chunk) => {
|
|
|
|
chunks.push(chunk);
|
|
|
|
});
|
|
|
|
res.on('end', () => {
|
|
|
|
onClose();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
req.on('error', (e) => {
|
|
|
|
log('error', 'HTTP error', opts.method, config.url, e);
|
|
|
|
return config.error && config.error('network error', {});
|
|
|
|
});
|
|
|
|
req.on('timeout', () => {
|
|
|
|
req.abort();
|
|
|
|
return config.error && config.error('timeout', {});
|
|
|
|
});
|
|
|
|
if (data) {
|
|
|
|
req.write(data);
|
|
|
|
}
|
|
|
|
req.end();
|
|
|
|
}
|
2021-01-08 22:54:45 +01:00
|
|
|
|
2021-01-10 14:31:33 +01:00
|
|
|
function setupIpcHandlers() {
|
|
|
|
const { setupIpcHandlers } = require('./scripts/ipc');
|
|
|
|
setupIpcHandlers();
|
2021-01-10 17:16:21 +01:00
|
|
|
logProgress('setting ipc handlers');
|
2021-01-10 14:31:33 +01:00
|
|
|
}
|
|
|
|
|
2021-01-08 22:54:45 +01:00
|
|
|
function exitAndStartUpdate() {
|
|
|
|
if (pendingUpdateFilePath) {
|
2021-01-09 13:30:19 +01:00
|
|
|
const { installUpdate } = require('./scripts/update-installer');
|
2021-01-09 13:15:25 +01:00
|
|
|
installUpdate(pendingUpdateFilePath);
|
2021-01-08 22:54:45 +01:00
|
|
|
}
|
|
|
|
}
|