From 7a6ddc0f17447e37cb4728242bff259c6661f328 Mon Sep 17 00:00:00 2001 From: nig Date: Thu, 27 Feb 2020 14:39:44 +0100 Subject: [PATCH] [desktop] fix #1829 - make tray icon functionality available in the web app - windows now minimize instead of hiding to keep them accessible even when there is no tray icon - remove redundant window list for catalina --- flow/api/types.js | 2 + package-lock.json | 565 ++++++++---------- package.json | 8 +- src/desktop/ApplicationWindow.js | 8 +- src/desktop/DesktopMain.js | 4 +- src/desktop/DesktopNotifier.js | 3 +- src/desktop/DesktopTray.js | 120 ---- src/desktop/DesktopWindowManager.js | 11 +- src/desktop/ElectronUpdater.js | 2 +- src/desktop/IPC.js | 5 +- src/desktop/sse/DesktopAlarmStorage.js | 4 +- src/desktop/tray/DesktopTray.js | 93 +++ src/desktop/tray/PlatformDock.js | 57 ++ src/desktop/tray/PlatformTray.js | 66 ++ src/gui/base/icons/Icons.js | 4 + src/gui/base/icons/Logo.js | 12 +- src/gui/nav/DrawerMenu.js | 25 +- src/misc/TranslationKey.js | 3 + src/settings/DesktopSettingsViewer.js | 5 +- src/translations/en.js | 4 +- test/client/desktop/ApplicationWindowTest.js | 5 +- test/client/desktop/DesktopTrayTest.js | 25 +- .../desktop/DesktopWindowManagerTest.js | 58 +- test/client/desktop/ElectronUpdaterTest.js | 117 ++-- test/client/desktop/IPCTest.js | 18 + 25 files changed, 636 insertions(+), 588 deletions(-) delete mode 100644 src/desktop/DesktopTray.js create mode 100644 src/desktop/tray/DesktopTray.js create mode 100644 src/desktop/tray/PlatformDock.js create mode 100644 src/desktop/tray/PlatformTray.js diff --git a/flow/api/types.js b/flow/api/types.js index d7a5ed0c75e3..729e4e06b46c 100644 --- a/flow/api/types.js +++ b/flow/api/types.js @@ -146,6 +146,7 @@ type MainRequestType = 'execNative' | 'updateWebSocketState' | 'counterUpdate' | 'infoMessage' + type NativeRequestType = 'init' | 'generateRsaKey' | 'rsaEncrypt' @@ -193,6 +194,7 @@ type NativeRequestType = 'init' | 'unIntegrateDesktop' | 'unscheduleAlarms' | 'setSearchOverlayState' + | 'closeApp' | 'unload' // desktop diff --git a/package-lock.json b/package-lock.json index 539af1b29c4d..ab3e2ac57d99 100644 --- a/package-lock.json +++ b/package-lock.json @@ -104,9 +104,9 @@ "dev": true }, "@types/fs-extra": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-8.0.1.tgz", - "integrity": "sha512-J00cVDALmi/hJOYsunyT52Hva5TnJeKP5yd1r+mH/ZU0mbYZflR0Z5kw5kITtKTRYMhm1JMClOFYdHnQszEvqw==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-UoOfVEzAUpeSPmjm7h1uk5MH6KZma2z2O7a75onTGjnNvAvMVrPzPL/vBbT65iIGHWj6rokwfmYcmxmlSf2uwg==", "dev": true, "requires": { "@types/node": "*" @@ -119,10 +119,13 @@ "dev": true }, "@types/semver": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-6.2.0.tgz", - "integrity": "sha512-1OzrNb4RuAzIT7wHSsgZRlMBlNsJl+do6UblR7JMW4oB7bbR+uBEYtUh7gEc/jM84GGilh68lSOokyM/zNUlBA==", - "dev": true + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.1.0.tgz", + "integrity": "sha512-pOKLaubrAEMUItGNpgwl0HMFPrSAFic8oSVIvfu1UwcgGNmNyK9gyhBHKmBnUTwwVvpZfkzUC0GaMgnL6P86uA==", + "dev": true, + "requires": { + "@types/node": "*" + } }, "@types/yargs": { "version": "15.0.3", @@ -256,21 +259,21 @@ "dev": true }, "app-builder-lib": { - "version": "22.3.3", - "resolved": "https://registry.npmjs.org/app-builder-lib/-/app-builder-lib-22.3.3.tgz", - "integrity": "sha512-zZJyuF3djIA5K6tbx8t3w40M0iVoBR6K2k4KMHOu96+ffmfvdlu+UrsvDqvP1N1cgwFoSSyvW/Hg9/SP12pnEQ==", + "version": "22.3.5", + "resolved": "https://registry.npmjs.org/app-builder-lib/-/app-builder-lib-22.3.5.tgz", + "integrity": "sha512-67OCx1TNnesunY+vUeAhzm9BMg5o8FmiFzrzi0aCp9yfO32L8d9d9GDhNCStHJli6hBBu1ckOxixnIwL+FU+Cg==", "dev": true, "requires": { "7zip-bin": "~5.0.3", "@develar/schema-utils": "~2.1.0", "async-exit-hook": "^2.0.1", "bluebird-lst": "^1.0.9", - "builder-util": "22.3.3", - "builder-util-runtime": "8.6.0", + "builder-util": "22.3.5", + "builder-util-runtime": "8.6.1", "chromium-pickle-js": "^0.2.0", "debug": "^4.1.1", "ejs": "^3.0.1", - "electron-publish": "22.3.3", + "electron-publish": "22.3.5", "fs-extra": "^8.1.0", "hosted-git-info": "^3.0.2", "is-ci": "^2.0.0", @@ -281,7 +284,7 @@ "normalize-package-data": "^2.5.0", "read-config-file": "5.0.1", "sanitize-filename": "^1.6.3", - "semver": "^7.1.1", + "semver": "^7.1.3", "temp-file": "^3.3.6" }, "dependencies": { @@ -294,23 +297,6 @@ "ms": "^2.1.1" } }, - "fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", - "dev": true, - "requires": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - } - }, - "graceful-fs": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", - "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", - "dev": true - }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -318,9 +304,9 @@ "dev": true }, "semver": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.1.2.tgz", - "integrity": "sha512-BJs9T/H8sEVHbeigqzIEo57Iu/3DG6c4QoqTfbQB3BPA4zgzAomh/Fk9E7QtjWQ8mx2dgA9YCfSF4y9k9bHNpQ==", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.1.3.tgz", + "integrity": "sha512-ekM0zfiA9SCBlsKa2X1hxyxiI4L3B6EbVJkkdgQXnSEEaHlGdvyodMruTiulSRWMMB4NeIuYNMC9rTKTz97GxA==", "dev": true } } @@ -1265,9 +1251,9 @@ }, "dependencies": { "readable-stream": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.5.0.tgz", - "integrity": "sha512-gSz026xs2LfxBPudDuI41V1lka8cxg64E66SGe78zJlsUofOg/yqwezdIcdfwik6B4h8LFmWPA9ef9X3FiNFLA==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", "requires": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -1294,14 +1280,6 @@ "dev": true, "requires": { "bluebird": "^3.5.5" - }, - "dependencies": { - "bluebird": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", - "dev": true - } } }, "body-parser": { @@ -1350,12 +1328,6 @@ "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", "dev": true }, - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - }, "emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -1440,17 +1412,17 @@ "dev": true }, "builder-util": { - "version": "22.3.3", - "resolved": "https://registry.npmjs.org/builder-util/-/builder-util-22.3.3.tgz", - "integrity": "sha512-VzQALenLDdeaz7hXaQgS9N0Xz3zlgkK64Dp2Vn61XTbhI0MgVneTeEKKDFwdBC/l7v0cHsOPeao/xeWmyznC2g==", + "version": "22.3.5", + "resolved": "https://registry.npmjs.org/builder-util/-/builder-util-22.3.5.tgz", + "integrity": "sha512-usAvhyAdHDgKXfP+tInnHkVWli+8NRSvXEf2xgcbVWkDsi/XTY9GTS/JhvjcEF5kNkjZOEcGvuGh3qfZsUI/dQ==", "dev": true, "requires": { "7zip-bin": "~5.0.3", "@types/debug": "^4.1.5", - "@types/fs-extra": "^8.0.1", + "@types/fs-extra": "^8.1.0", "app-builder-bin": "3.5.2", "bluebird-lst": "^1.0.9", - "builder-util-runtime": "8.6.0", + "builder-util-runtime": "8.6.1", "chalk": "^3.0.0", "debug": "^4.1.1", "fs-extra": "^8.1.0", @@ -1470,23 +1442,6 @@ "ms": "^2.1.1" } }, - "fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", - "dev": true, - "requires": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - } - }, - "graceful-fs": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", - "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", - "dev": true - }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -1512,9 +1467,9 @@ } }, "builder-util-runtime": { - "version": "8.6.0", - "resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-8.6.0.tgz", - "integrity": "sha512-WTDhTUVrm7zkFyd6Qn7AXgmWifjpZ/fYnEdV3XCOIDMNNb/KPddBTbQ8bUlxxVeuOYlhGpcLUypG+4USdGL1ww==", + "version": "8.6.1", + "resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-8.6.1.tgz", + "integrity": "sha512-gwIUtMaICmc+e2EC3u3byXcwCyfhtG40LJRNnGfs8AYqacKl4ZLP50ab+uDttn7QAXe0LfMAuKz9v8bCODV0yg==", "dev": true, "requires": { "debug": "^4.1.1", @@ -1613,6 +1568,12 @@ "integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA=", "dev": true }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, "caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", @@ -1875,6 +1836,57 @@ "integrity": "sha512-tgU3fKwzYjiLEQgPMD9Jt+JjHVL9kW93FiIMX/l7rivvOD4/LL0Mf7gda3+4U2KJBloybwgj5KEoQgGRioMiKQ==", "dev": true }, + "cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + } + } + }, "clone": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", @@ -2001,12 +2013,12 @@ } }, "configstore": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.0.tgz", - "integrity": "sha512-eE/hvMs7qw7DlcB5JPRnthmrITuHMmACUJAp89v6PT6iOqzoLS7HRWhBtuHMlhNHo2AhUSA/3Dh1bKNJHcublQ==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", + "integrity": "sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==", "dev": true, "requires": { - "dot-prop": "^5.1.0", + "dot-prop": "^5.2.0", "graceful-fs": "^4.1.2", "make-dir": "^3.0.0", "unique-string": "^2.0.0", @@ -2286,37 +2298,20 @@ "dev": true }, "dmg-builder": { - "version": "22.3.3", - "resolved": "https://registry.npmjs.org/dmg-builder/-/dmg-builder-22.3.3.tgz", - "integrity": "sha512-5AA6pukcezK335AVHkEJYyukv2hw14bOgGFWHyfrW4U9Ah1FhE5Z9a53VNW/8rGAcBaoctst8oMLITo0ZkiXQQ==", + "version": "22.3.5", + "resolved": "https://registry.npmjs.org/dmg-builder/-/dmg-builder-22.3.5.tgz", + "integrity": "sha512-CmiJwVfipTzj2YhACPVJuR3PBTIknwuCMP+bl+ceLF5ETwG9RSkXBZT45XHe2RT4nM8/jkXttO/6UPiNOFBa+A==", "dev": true, "requires": { - "app-builder-lib": "~22.3.3", + "app-builder-lib": "~22.3.5", "bluebird-lst": "^1.0.9", - "builder-util": "~22.3.3", + "builder-util": "~22.3.5", "fs-extra": "^8.1.0", "iconv-lite": "^0.5.1", "js-yaml": "^3.13.1", "sanitize-filename": "^1.6.3" }, "dependencies": { - "fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", - "dev": true, - "requires": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - } - }, - "graceful-fs": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", - "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", - "dev": true - }, "iconv-lite": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.5.1.tgz", @@ -2383,9 +2378,9 @@ "dev": true }, "electron": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/electron/-/electron-8.0.0.tgz", - "integrity": "sha512-vBXUKRqTUq0jv1upvISdvScDDH3uCPwXj4eA5BeR3UDbJp2hOhq7eJxwjIQbfLQql98aYz4X6pSlzBnhfyQqHA==", + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/electron/-/electron-8.0.2.tgz", + "integrity": "sha512-hiQaFtFhd9X2Vjs01l3GXb8hPWSCa31o/kXydo+RC7vwcx9AGuzG7jWIq8vidzAWsF/YPM0LFVjFRZrfkqi03Q==", "dev": true, "requires": { "@electron/get": "^1.0.1", @@ -2394,120 +2389,33 @@ }, "dependencies": { "@types/node": { - "version": "12.12.26", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.26.tgz", - "integrity": "sha512-UmUm94/QZvU5xLcUlNR8hA7Ac+fGpO1EG/a8bcWVz0P0LqtxFmun9Y2bbtuckwGboWJIT70DoWq1r3hb56n3DA==", + "version": "12.12.28", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.28.tgz", + "integrity": "sha512-g73GJYJDXgf0jqg+P9S8h2acWbDXNkoCX8DLtJVu7Fkn788pzQ/oJsrdJz/2JejRf/SjfZaAhsw+3nd1D5EWGg==", "dev": true } } }, "electron-builder": { - "version": "22.3.3", - "resolved": "https://registry.npmjs.org/electron-builder/-/electron-builder-22.3.3.tgz", - "integrity": "sha512-0JvxZFL/lP8ph5QZZZBw0KULXY4Cc67cza4hF6WX3YWXfXo/+9FSSF6VYB9nldHPN2C03TpKWAqwncV66qd28A==", + "version": "22.3.5", + "resolved": "https://registry.npmjs.org/electron-builder/-/electron-builder-22.3.5.tgz", + "integrity": "sha512-xwi1dy0KaHsF8M8z65wtq8364YxOMSuCzoVDmgGJGjIiYrn3Ww7FNarEZgf6EGqjDrA9NCmjRf2PpK1iSbiO1w==", "dev": true, "requires": { - "@types/yargs": "^15.0.1", - "app-builder-lib": "22.3.3", + "@types/yargs": "^15.0.3", + "app-builder-lib": "22.3.5", "bluebird-lst": "^1.0.9", - "builder-util": "22.3.3", - "builder-util-runtime": "8.6.0", + "builder-util": "22.3.5", + "builder-util-runtime": "8.6.1", "chalk": "^3.0.0", - "dmg-builder": "22.3.3", + "dmg-builder": "22.3.5", "fs-extra": "^8.1.0", "is-ci": "^2.0.0", "lazy-val": "^1.0.4", "read-config-file": "5.0.1", "sanitize-filename": "^1.6.3", - "update-notifier": "^4.0.0", + "update-notifier": "^4.1.0", "yargs": "^15.1.0" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true - }, - "cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "dev": true, - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - } - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", - "dev": true, - "requires": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - } - }, - "graceful-fs": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", - "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "string-width": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", - "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - } - }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.0" - } - }, - "yargs": { - "version": "15.1.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.1.0.tgz", - "integrity": "sha512-T39FNN1b6hCW4SOIk1XyTOWxtXdcen0t+XYrysQmChzSipvhBO8Bj0nK1ozAasdk24dNWuMZvr4k24nz+8HHLg==", - "dev": true, - "requires": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^16.1.0" - } - } } }, "electron-is-accelerator": { @@ -2686,38 +2594,21 @@ } }, "electron-publish": { - "version": "22.3.3", - "resolved": "https://registry.npmjs.org/electron-publish/-/electron-publish-22.3.3.tgz", - "integrity": "sha512-QfdS6gyqdjX+JBm3DhRT8nwO2TKQF9Z2dsZBXxCfE+FXYe2XmxMXWeXY2vPBHxSOpBYeAYVIkBiNL+gWcSfA+w==", + "version": "22.3.5", + "resolved": "https://registry.npmjs.org/electron-publish/-/electron-publish-22.3.5.tgz", + "integrity": "sha512-zVpDd/+t6f9dLuDmw6avp2YsfnYZtUOAZ2tAVrVUnrzYqBWVIvw/yyXcuWBZvOFR3ecmNCxHRFL2GvlFUGNYkg==", "dev": true, "requires": { - "@types/fs-extra": "^8.0.1", + "@types/fs-extra": "^8.1.0", "bluebird-lst": "^1.0.9", - "builder-util": "~22.3.3", - "builder-util-runtime": "8.6.0", + "builder-util": "~22.3.5", + "builder-util-runtime": "8.6.1", "chalk": "^3.0.0", "fs-extra": "^8.1.0", "lazy-val": "^1.0.4", "mime": "^2.4.4" }, "dependencies": { - "fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", - "dev": true, - "requires": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - } - }, - "graceful-fs": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", - "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", - "dev": true - }, "mime": { "version": "2.4.4", "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.4.tgz", @@ -2931,42 +2822,25 @@ } }, "electron-updater": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-4.2.1.tgz", - "integrity": "sha512-9rXEiOnODv+SqzKgzcaiusQH3sHaWMHn0afGDPrkDNN4zoZLUQvyefqQEs7a7fxnwXmP/kuNkn65PZkiyM8X2A==", + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-4.2.4.tgz", + "integrity": "sha512-iqN0uoP2+Nkiljp/o4DzZVeSpOOCMtP8+pqL5/qDI+1/ARW99T2TFcpRrPwX4dntowNV7X5T19aKFLK3+9AdkA==", "dev": true, "requires": { - "@types/semver": "^6.2.0", - "builder-util-runtime": "8.6.0", + "@types/semver": "^7.1.0", + "builder-util-runtime": "8.6.1", "fs-extra": "^8.1.0", "js-yaml": "^3.13.1", "lazy-val": "^1.0.4", "lodash.isequal": "^4.5.0", - "pako": "^1.0.10", - "semver": "^7.1.1" + "pako": "^1.0.11", + "semver": "^7.1.3" }, "dependencies": { - "fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", - "dev": true, - "requires": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - } - }, - "graceful-fs": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", - "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", - "dev": true - }, "semver": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.1.1.tgz", - "integrity": "sha512-WfuG+fl6eh3eZ2qAf6goB7nhiCd7NPXhmyFxigB/TOkQyeLP8w8GsVehvtGNtnNmyboz4TgeK40B1Kbql/8c5A==", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.1.3.tgz", + "integrity": "sha512-ekM0zfiA9SCBlsKa2X1hxyxiI4L3B6EbVJkkdgQXnSEEaHlGdvyodMruTiulSRWMMB4NeIuYNMC9rTKTz97GxA==", "dev": true } } @@ -3137,6 +3011,12 @@ "esniff": "^1.1" } }, + "escape-goat": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-2.1.1.tgz", + "integrity": "sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==", + "dev": true + }, "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -3590,12 +3470,14 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -3610,17 +3492,20 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -3737,7 +3622,8 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -3749,6 +3635,7 @@ "version": "1.0.0", "bundled": true, "dev": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -3763,6 +3650,7 @@ "version": "3.0.4", "bundled": true, "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -3770,12 +3658,14 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "minipass": { "version": "2.3.5", "bundled": true, "dev": true, + "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -3794,6 +3684,7 @@ "version": "0.5.1", "bundled": true, "dev": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -3874,7 +3765,8 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -3886,6 +3778,7 @@ "version": "1.4.0", "bundled": true, "dev": true, + "optional": true, "requires": { "wrappy": "1" } @@ -4007,6 +3900,7 @@ "version": "1.0.2", "bundled": true, "dev": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -4913,9 +4807,9 @@ "dev": true }, "keytar": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/keytar/-/keytar-5.2.0.tgz", - "integrity": "sha512-vsIX6n2BgTwzbKOSPIiJ8YduwHlPEE/G5dkmZWXaQK9qiGZMQyhxlFA4O6vrvM5fsXTMgUOrODYAqgpfNSRLDw==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/keytar/-/keytar-5.4.0.tgz", + "integrity": "sha512-Ta0RtUmkq7un177SPgXKQ7FGfGDV4xvsV0cGNiWVEzash5U0wyOsXpwfrK2+Oq+hHvsvsbzIZUUuJPimm3avFw==", "requires": { "nan": "2.14.0", "prebuild-install": "5.3.3" @@ -5098,9 +4992,9 @@ "integrity": "sha512-3sLvlfbFo+AxVEY3IqxymbumtnlgBwjDExxK60W3d+trrUzErNAz/PfvPT+mva+vEUrdIodeCOs7fB6zHtRSrw==" }, "make-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.0.0.tgz", - "integrity": "sha512-grNJDhb8b1Jm1qeqW5R/O63wUo4UXo2v2HMic6YT9i/HBlF93S8jkMgH7yugvY9ABDShH4VZMn8I+U8+fCNegw==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.0.2.tgz", + "integrity": "sha512-rYKABKutXa6vXTXhoV18cBE7PaewPXHe/Bdq4v+ZLMhxbWApkFFplT0LcbMW+6BbjnQXzZ/sAvSE/JdguApG5w==", "dev": true, "requires": { "semver": "^6.0.0" @@ -5838,9 +5732,9 @@ } }, "pako": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.10.tgz", - "integrity": "sha512-0DTvPVU3ed8+HNXOu5Bs+o//Mbdj9VNQMUOe9oKCwh8l0GNwpTDMKCWbRjgtD291AWnkAgkqA/LOnQS8AmS1tw==", + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", "dev": true }, "parse-author": { @@ -6085,6 +5979,15 @@ "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", "dev": true }, + "pupa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pupa/-/pupa-2.0.1.tgz", + "integrity": "sha512-hEJH0s8PXLY/cdXh66tNEQGndDrIKNqNC5xmrysZy3i5C3oEoLna7YAOad+7u125+zH1HNXUmGEkrhb3c2VriA==", + "dev": true, + "requires": { + "escape-goat": "^2.0.0" + } + }, "qrcode-svg": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/qrcode-svg/-/qrcode-svg-1.0.0.tgz", @@ -6177,23 +6080,6 @@ "lazy-val": "^1.0.4" }, "dependencies": { - "fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", - "dev": true, - "requires": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - } - }, - "graceful-fs": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", - "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", - "dev": true - }, "json5": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.1.tgz", @@ -7057,9 +6943,9 @@ } }, "mimic-response": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.0.0.tgz", - "integrity": "sha512-8ilDoEapqA4uQ3TwS0jakGONKXVJqpy+RpM+3b7pLdOjghCrEiGp9SRkFbUHAmZW9vdnrENWHjaweIoTIJExSQ==" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", + "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==" } } }, @@ -7708,9 +7594,9 @@ }, "dependencies": { "readable-stream": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.5.0.tgz", - "integrity": "sha512-gSz026xs2LfxBPudDuI41V1lka8cxg64E66SGe78zJlsUofOg/yqwezdIcdfwik6B4h8LFmWPA9ef9X3FiNFLA==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", "requires": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -7727,25 +7613,6 @@ "requires": { "async-exit-hook": "^2.0.1", "fs-extra": "^8.1.0" - }, - "dependencies": { - "fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", - "dev": true, - "requires": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - } - }, - "graceful-fs": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", - "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", - "dev": true - } } }, "term-size": { @@ -8120,14 +7987,14 @@ } }, "update-notifier": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-4.0.0.tgz", - "integrity": "sha512-p9zf71hWt5GVXM4iEBujpUgx8mK9AWiCCapEJm/O1z5ntCim83Z1ATqzZFBHFYqx03laMqv8LiDgs/7ikXjf/g==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-4.1.0.tgz", + "integrity": "sha512-w3doE1qtI0/ZmgeoDoARmI5fjDoT93IfKgEGqm26dGUOh8oNpaSTsGNdYRN/SjOuo10jcJGwkEL3mroKzktkew==", "dev": true, "requires": { "boxen": "^4.2.0", "chalk": "^3.0.0", - "configstore": "^5.0.0", + "configstore": "^5.0.1", "has-yarn": "^2.1.0", "import-lazy": "^2.1.0", "is-ci": "^2.0.0", @@ -8135,6 +8002,7 @@ "is-npm": "^4.0.0", "is-yarn-global": "^0.3.0", "latest-version": "^5.0.0", + "pupa": "^2.0.1", "semver-diff": "^3.1.1", "xdg-basedir": "^4.0.0" } @@ -8399,9 +8267,9 @@ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "write-file-atomic": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.1.tgz", - "integrity": "sha512-JPStrIyyVJ6oCSz/691fAjFtefZ6q+fP6tm+OS4Qw6o+TGQxNp1ziY2PgS+X/m0V8OWhZiO/m4xSj+Pr4RrZvw==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", "dev": true, "requires": { "imurmurhash": "^0.1.4", @@ -8468,6 +8336,65 @@ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "dev": true }, + "yargs": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.1.0.tgz", + "integrity": "sha512-T39FNN1b6hCW4SOIk1XyTOWxtXdcen0t+XYrysQmChzSipvhBO8Bj0nK1ozAasdk24dNWuMZvr4k24nz+8HHLg==", + "dev": true, + "requires": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^16.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + } + } + }, "yargs-parser": { "version": "16.1.0", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-16.1.0.tgz", diff --git a/package.json b/package.json index 5ea1f59b998d..06c84ecf7791 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "squire-rte": "1.9.0", "systemjs": "0.20.19", "luxon": "^1.22.0", - "keytar": "5.2.0" + "keytar": "5.4.0" }, "devDependencies": { "babel-core": "6.26.3", @@ -38,13 +38,13 @@ "chokidar-socket-emitter": "0.6.0", "commander": "5.0.0-2", "core-js": "2.6.10", - "electron": "8.0.0", - "electron-builder": "22.3.3", + "electron": "8.0.2", + "electron-builder": "22.3.5", "electron-localshortcut": "3.2.1", "electron-notarize": "0.2.1", "electron-packager": "14.2.1", "electron-rebuild": "1.10.0", - "electron-updater": "4.2.1", + "electron-updater": "4.2.4", "express": "4.17.1", "flow-bin": "0.77.0", "fs-extra": "8.1.0", diff --git a/src/desktop/ApplicationWindow.js b/src/desktop/ApplicationWindow.js index b738043044b4..214e0b960b69 100644 --- a/src/desktop/ApplicationWindow.js +++ b/src/desktop/ApplicationWindow.js @@ -19,7 +19,6 @@ export type UserInfo = {| export class ApplicationWindow { _ipc: IPC; - _wm: WindowManager; _startFile: string; _browserWindow: BrowserWindow; _preloadjs: string; @@ -34,7 +33,6 @@ export class ApplicationWindow { id: number; constructor(wm: WindowManager, preloadjs: string, desktophtml: string, noAutoLogin?: boolean) { - this._wm = wm this._userInfo = null this._ipc = wm.ipc this._preloadjs = preloadjs @@ -54,7 +52,7 @@ export class ApplicationWindow { {key: Keys.F11, exec: () => this._toggleFullScreen(), help: "toggleFullScreen_action"}, {key: Keys.RIGHT, alt: true, exec: () => this._browserWindow.webContents.goForward(), help: "pageForward_label"}, {key: Keys.LEFT, alt: true, exec: () => this._tryGoBack(), help: "pageBackward_label"}, - {key: Keys.H, ctrl: true, exec: () => wm.hide(), help: "hideWindows_action"}, + {key: Keys.H, ctrl: true, exec: () => wm.minimize(), help: "hideWindows_action"}, ]) console.log("startFile: ", this._startFile) @@ -190,10 +188,10 @@ export class ApplicationWindow { }) // Shortcuts but be registered here, before "focus" or "blur" event fires, otherwise localShortcut fails - this._reRegisterShorctus(wm) + this._reRegisterShorctus() } - _reRegisterShorctus(wm: WindowManager) { + _reRegisterShorctus() { localShortcut.unregisterAll(this._browserWindow) this._shortcuts.forEach(s => { // build the accelerator string localShortcut understands diff --git a/src/desktop/DesktopMain.js b/src/desktop/DesktopMain.js index 3bc759694096..e145bdd8aba4 100644 --- a/src/desktop/DesktopMain.js +++ b/src/desktop/DesktopMain.js @@ -5,10 +5,8 @@ import {DesktopConfigHandler} from './DesktopConfigHandler' import {app} from 'electron' import DesktopUtils from './DesktopUtils.js' import {IPC} from './IPC.js' -import PreloadImports from './PreloadImports.js' import {WindowManager} from "./DesktopWindowManager" import {DesktopNotifier} from "./DesktopNotifier" -import {DesktopTray} from './DesktopTray.js' import {ElectronUpdater} from "./ElectronUpdater" import {DesktopSseClient} from "./sse/DesktopSseClient" import {Socketeer} from "./Socketeer" @@ -20,6 +18,8 @@ import en from "../translations/en" import {DesktopNetworkClient} from "./DesktopNetworkClient" import {DesktopCryptoFacade} from "./DesktopCryptoFacade" import {DesktopDownloadManager} from "./DesktopDownloadManager" +import {DesktopTray} from "./tray/DesktopTray" +import PreloadImports from "./PreloadImports" mp() diff --git a/src/desktop/DesktopNotifier.js b/src/desktop/DesktopNotifier.js index 03e9ca0385f8..a4743ef647df 100644 --- a/src/desktop/DesktopNotifier.js +++ b/src/desktop/DesktopNotifier.js @@ -1,7 +1,7 @@ // @flow import type {NativeImage} from 'electron' import {Notification} from 'electron' -import {DesktopTray} from "./DesktopTray" +import {DesktopTray} from "./tray/DesktopTray" import type {ApplicationWindow} from "./ApplicationWindow" import {neverNull} from "../api/common/utils/Utils" import type {NotificationResultEnum} from "./DesktopConstants" @@ -107,7 +107,6 @@ export class DesktopNotifier { body?: string, icon?: NativeImage |}, onClick: (res: NotificationResultEnum) => void): () => void { - const {title, body, icon} = Object.assign({}, {body: "", icon: this._tray.getIcon()}, props) diff --git a/src/desktop/DesktopTray.js b/src/desktop/DesktopTray.js deleted file mode 100644 index 2c27409c36a8..000000000000 --- a/src/desktop/DesktopTray.js +++ /dev/null @@ -1,120 +0,0 @@ -// @flow -import type {NativeImage} from 'electron' -import {app, Menu, MenuItem, nativeImage, Tray} from 'electron' -import path from 'path' -import {lang} from "../misc/LanguageViewModel" -import type {DesktopConfigHandler} from './DesktopConfigHandler.js' -import type {WindowManager} from "./DesktopWindowManager.js" -import type {DesktopNotifier} from "./DesktopNotifier.js" -import {neverNull} from "../api/common/utils/Utils"; - -let icon: NativeImage - -export class DesktopTray { - _conf: DesktopConfigHandler; - _wm: WindowManager; - _notifier: DesktopNotifier; - - _tray: ?Tray; - - constructor(config: DesktopConfigHandler, notifier: DesktopNotifier) { - this._conf = config - this._notifier = notifier - this.getIcon() - app.on('will-quit', (e: Event) => { - if (this._tray) { - this._tray.destroy() - this._tray = null - } - }) - } - - /** - * linux env: DESKTOP_SESSION XDG_SESSION_DESKTOP XDG_CURRENT_DESKTOP to detect WM - */ - update(): void { - if (!this._conf.getDesktopConfig('runAsTrayApp')) { - return - } - if (process.platform === 'darwin') { // we use the dock on MacOs - app.dock.setMenu(this._getMenu()) - if (!app.dock.isVisible()) { - app.dock.show() - } - } else { - if (!this._tray) { - this._tray = new Tray(this.getIcon()) - this._tray.on('click', ev => { - this._wm.getLastFocused(true) - }) - } - // OK, we just created one if it wasn't there - neverNull(this._tray).setContextMenu(this._getMenu()) - } - } - - setBadge() { - if (process.platform !== "darwin") return - app.dock.bounce() - app.dock.setBadge("●") - } - - clearBadge() { - if (process.platform !== "darwin") return - app.dock.setBadge("") - } - - getIcon(): NativeImage { - return DesktopTray.getIcon(this._conf.get('iconName')) - } - - static getIcon(iconName: string): NativeImage { - if (icon) { - return icon - } else if (process.platform === 'darwin') { - icon = nativeImage.createFromPath(path.join((process: any).resourcesPath, `icons/${iconName}.icns`)) - } else if (process.platform === 'win32') { - icon = nativeImage.createFromPath(path.join((process: any).resourcesPath, `icons/${iconName}`)) - } else { - icon = nativeImage.createFromPath(path.join((process: any).resourcesPath, `icons/${iconName}`)) - } - return icon - } - - _getMenu(): Menu { - const m = new Menu() - m.append(new MenuItem({ - label: lang.get("openNewWindow_action"), click: () => { - this._wm.newWindow(true) - } - })) - if (this._wm.getAll().length > 0) { - m.append(new MenuItem({type: 'separator'})) - this._wm.getAll().forEach(w => { - let label = w.getTitle() - if (this._notifier.hasNotificationsForWindow(w)) { - label = "• " + label - } else { - label = label + " " - } - m.append(new MenuItem({ - label: label, - click: () => w.show() - })) - }) - } - if (process.platform !== 'darwin') { - m.append(new MenuItem({type: 'separator'})) - m.append(new MenuItem({ - label: lang.get("quit_action"), - accelerator: "CmdOrCtrl+Q", - click: app.quit - })) - } - return m - } - - setWindowManager(wm: WindowManager) { - this._wm = wm - } -} diff --git a/src/desktop/DesktopWindowManager.js b/src/desktop/DesktopWindowManager.js index 0c06a79aeb08..e5e8b630118f 100644 --- a/src/desktop/DesktopWindowManager.js +++ b/src/desktop/DesktopWindowManager.js @@ -6,7 +6,7 @@ import path from 'path' import type {UserInfo} from "./ApplicationWindow" import {ApplicationWindow} from "./ApplicationWindow" import type {DesktopConfigHandler} from "./DesktopConfigHandler" -import {DesktopTray} from "./DesktopTray" +import {DesktopTray} from "./tray/DesktopTray" import type {DesktopNotifier} from "./DesktopNotifier.js" import {LOGIN_TITLE} from "../api/Env" import type {DesktopDownloadManager} from "./DesktopDownloadManager" @@ -51,11 +51,6 @@ export class WindowManager { ) windows.unshift(w) w.on('close', ev => { - // we don't want to actually close windows where someone is logged in, just hide them - if (this._conf.getDesktopConfig('runAsTrayApp') && w.getUserInfo() != null && !forceQuit) { - ev.preventDefault() - w.hide() - } this.saveBounds(w) }).on('closed', ev => { windows.splice(windows.indexOf(w), 1) @@ -94,6 +89,10 @@ export class WindowManager { } } + minimize() { + windows.forEach(w => w.minimize()) + } + getIcon(): NativeImage { return DesktopTray.getIcon(this._conf.get('iconName')) } diff --git a/src/desktop/ElectronUpdater.js b/src/desktop/ElectronUpdater.js index 8034fa2c6456..eb9b5b15c9c7 100644 --- a/src/desktop/ElectronUpdater.js +++ b/src/desktop/ElectronUpdater.js @@ -8,7 +8,7 @@ import {lang} from '../misc/LanguageViewModel' import type {DesktopConfigHandler} from './DesktopConfigHandler' import {neverNull} from "../api/common/utils/Utils" import {UpdateError} from "../api/common/error/UpdateError" -import {DesktopTray} from "./DesktopTray" +import {DesktopTray} from "./tray/DesktopTray" export class ElectronUpdater { _conf: DesktopConfigHandler; diff --git a/src/desktop/IPC.js b/src/desktop/IPC.js index 000af9d9450a..0a3b8f24d45a 100644 --- a/src/desktop/IPC.js +++ b/src/desktop/IPC.js @@ -1,5 +1,5 @@ // @flow -import {dialog, ipcMain} from 'electron' +import {app, dialog, ipcMain} from 'electron' import type {WindowManager} from "./DesktopWindowManager.js" import {err} from './DesktopErrorHandler.js' import {defer} from '../api/common/utils/Utils.js' @@ -141,6 +141,9 @@ export class IPC { case 'openNewWindow': this._wm.newWindow(true) return Promise.resolve() + case 'closeApp': + app.quit() + return Promise.resolve() case 'showWindow': return this.initialized(windowId).then(() => { const w = this._wm.get(windowId) diff --git a/src/desktop/sse/DesktopAlarmStorage.js b/src/desktop/sse/DesktopAlarmStorage.js index 535025ce47d6..aa8ac18a2dee 100644 --- a/src/desktop/sse/DesktopAlarmStorage.js +++ b/src/desktop/sse/DesktopAlarmStorage.js @@ -4,12 +4,12 @@ import type {DeferredObject} from "../../api/common/utils/Utils" import {defer, downcast} from "../../api/common/utils/Utils" import {CryptoError} from '../../api/common/error/CryptoError' import type {DesktopConfigHandler} from "../DesktopConfigHandler" +import {DesktopConfigKey} from "../DesktopConfigHandler" import type {TimeoutData} from "./DesktopAlarmScheduler" import {elementIdPart} from "../../api/common/EntityFunctions" +import {DesktopCryptoFacade} from "../DesktopCryptoFacade" import {uint8ArrayToBitArray} from "../../api/worker/crypto/CryptoUtils" import {base64ToUint8Array} from "../../api/common/utils/Encoding" -import {DesktopCryptoFacade} from "../DesktopCryptoFacade" -import {DesktopConfigKey} from "../DesktopConfigHandler" const SERVICE_NAME = 'tutanota-vault' const ACCOUNT_NAME = 'tuta' diff --git a/src/desktop/tray/DesktopTray.js b/src/desktop/tray/DesktopTray.js new file mode 100644 index 000000000000..ddf55995e9f1 --- /dev/null +++ b/src/desktop/tray/DesktopTray.js @@ -0,0 +1,93 @@ +// @flow +import type {NativeImage} from 'electron' +import {app, Menu, MenuItem, nativeImage, Tray} from 'electron' +import type {DesktopConfigHandler} from '../DesktopConfigHandler.js' +import type {WindowManager} from "../DesktopWindowManager.js" +import type {DesktopNotifier} from "../DesktopNotifier.js" +import {lang} from "../../misc/LanguageViewModel" + +let icon: NativeImage +const platformTray: PlatformTray = process.platform === 'darwin' + ? require('./PlatformDock') + : require('./PlatformTray') + +export type PlatformTray = { + setBadge: ()=>void, + clearBadge: ()=>void, + getTray: (WindowManager, NativeImage) => ?Tray, + getPlatformMenuItems: ()=>Array, + attachMenuToTray: (Menu, ?Tray) => void, + iconPath: string => string, + needsWindowListInMenu: ()=>boolean +} + + +export class DesktopTray { + _conf: DesktopConfigHandler; + _wm: WindowManager; + _notifier: DesktopNotifier; + + _tray: ?Tray; + + constructor(config: DesktopConfigHandler, notifier: DesktopNotifier) { + this._conf = config + this._notifier = notifier + this.getIcon() + app.on('will-quit', (e: Event) => { + if (this._tray) { + this._tray.destroy() + this._tray = null + } + }).on('ready', () => { + if (!this._wm) console.warn("Tray: No WM set before 'ready'!") + this._tray = platformTray.getTray(this._wm, this.getIcon()) + }) + } + + update(): void { + if (!this._conf.getDesktopConfig("runAsTrayApp")) return + const m = new Menu() + m.append(new MenuItem({ + label: lang.get("openNewWindow_action"), click: () => { + this._wm.newWindow(true) + } + })) + if (platformTray.needsWindowListInMenu() && this._wm.getAll().length > 0) { + m.append(new MenuItem({type: 'separator'})) + this._wm.getAll().forEach(w => { + let label = w.getTitle() + if (this._notifier.hasNotificationsForWindow(w)) { + label = "• " + label + } else { + label = label + " " + } + m.append(new MenuItem({ + label: label, + click: () => w.show() + })) + }) + } + platformTray.getPlatformMenuItems().forEach(mi => m.append(mi)) + platformTray.attachMenuToTray(m, this._tray) + } + + setBadge() { + platformTray.setBadge() + } + + clearBadge() { + platformTray.clearBadge() + } + + getIcon(): NativeImage { + return DesktopTray.getIcon(this._conf.get('iconName')) + } + + static getIcon(iconName: string): NativeImage { + return icon || (icon = nativeImage.createFromPath(platformTray.iconPath(iconName))) + } + + setWindowManager(wm: WindowManager) { + this._wm = wm + } +} diff --git a/src/desktop/tray/PlatformDock.js b/src/desktop/tray/PlatformDock.js new file mode 100644 index 000000000000..27542c09670e --- /dev/null +++ b/src/desktop/tray/PlatformDock.js @@ -0,0 +1,57 @@ +// @flow + +import type {NativeImage} from "electron" +import {app, Menu, MenuItem, Tray} from "electron" +import type {PlatformTray} from "./DesktopTray" +import type {WindowManager} from "../DesktopWindowManager" +import path from "path" +import os from 'os' + +/* +* This file provides the functionality used by DesktopTray on mac + */ + +function needsWindowListInMenu() { + //MacOs Catalina started showing the window list on its own + return Number(os.release().slice(0,2)) < 19 +} + +function attachMenuToTray(m: Menu, tray: ?Tray): void { + app.dock.setMenu(m) +} + +function getPlatformMenuItems(): Array { + return [] +} + +function getTray(wm: WindowManager, icon: NativeImage): ?Tray { + if (!app.dock.isVisible()) { + app.dock.show() + } + return null +} + +function setBadge() { + app.dock.bounce() + app.dock.setBadge("●") +} + +function clearBadge() { + app.dock.setBadge("") +} + +function iconPath(iconName: string): string { + return path.join((process: any).resourcesPath, `icons/${iconName}.icns`) +} + +const platformTray: PlatformTray = { + getPlatformMenuItems, + getTray, + attachMenuToTray, + setBadge, + clearBadge, + iconPath, + needsWindowListInMenu +} + +module.exports = platformTray \ No newline at end of file diff --git a/src/desktop/tray/PlatformTray.js b/src/desktop/tray/PlatformTray.js new file mode 100644 index 000000000000..1900ff8df073 --- /dev/null +++ b/src/desktop/tray/PlatformTray.js @@ -0,0 +1,66 @@ +// @flow +import type {NativeImage} from 'electron' +import {app, Menu, MenuItem, Tray} from "electron" +import type {WindowManager} from "../DesktopWindowManager" +import {lang} from "../../misc/LanguageViewModel" +import type {PlatformTray} from './DesktopTray' +import path from "path" + +/* +* This file provides the functionality used by DesktopTray on windows & linux. + */ + +function attachMenuToTray(m: Menu, tray: ?Tray): void { + if (tray) tray.setContextMenu(m) +} + +function needsWindowListInMenu() { + return true +} + +function getPlatformMenuItems(): Array { + return [ + new MenuItem({type: 'separator'}), + new MenuItem({ + label: lang.get("quit_action"), + accelerator: "CmdOrCtrl+Q", + click: app.quit + }) + ] +} + +function getTray(wm: WindowManager, icon: NativeImage): Tray { + const tray = new Tray(icon) + /* + setting the context menu is necessary to prevent electron from segfaulting shortly after creating the tray. + workaround from: https://github.com/electron/electron/issues/22137#issuecomment-586105622 + issue: https://github.com/electron/electron/issues/22215 + */ + tray.setContextMenu(null) + tray.on('click', ev => { + wm.getLastFocused(true) + }) + return tray +} + + + +function setBadge() {} + +function clearBadge() {} + +function iconPath(iconName: string): string { + return path.join((process: any).resourcesPath, `icons/${iconName}`) +} + +const platformTray: PlatformTray = { + getPlatformMenuItems, + getTray, + attachMenuToTray, + setBadge, + clearBadge, + iconPath, + needsWindowListInMenu +} + +module.exports = platformTray \ No newline at end of file diff --git a/src/gui/base/icons/Icons.js b/src/gui/base/icons/Icons.js index 2c51ed63bf92..c6d7e1214a66 100644 --- a/src/gui/base/icons/Icons.js +++ b/src/gui/base/icons/Icons.js @@ -34,6 +34,7 @@ export const Icons = Object.freeze({ MatchCase: 'MatchCase', Mobile: 'Mobile', More: 'More', + NewWindow: 'NewWindow', Pin: 'Pin', Reply: 'Reply', ReplyAll: 'ReplyAll', @@ -61,6 +62,7 @@ export const Icons = Object.freeze({ Notifications: 'Notifications', ArrowDropLeft: 'ArrowDropLeft', ArrowDropRight: 'ArrowDropRight', + Power: 'Power', Palette: 'Palette', Import: 'Import', Export: 'Export', @@ -99,6 +101,7 @@ export const IconsSvg: {[IconsEnum]: string} = { MatchCase: '', Mobile: '', More: '', + NewWindow: '', Pin: '', Reply: '', ReplyAll: '', @@ -126,6 +129,7 @@ export const IconsSvg: {[IconsEnum]: string} = { Notifications: '', ArrowDropLeft: '', ArrowDropRight: '', + Power: '', Palette: '', Import: '', Export: '', diff --git a/src/gui/base/icons/Logo.js b/src/gui/base/icons/Logo.js index 92e5a24ef3e0..e969691c49e8 100644 --- a/src/gui/base/icons/Logo.js +++ b/src/gui/base/icons/Logo.js @@ -3,7 +3,13 @@ import {assertMainOrNodeBoot} from "../../../api/Env" assertMainOrNodeBoot() -export const LogoSvg = Object.freeze({ - Red: '', - Cyan: '' +export const Logo = Object.freeze({ + Red: 'Red', + Cyan: 'Cyan' }) +export type LogoEnum = $Values; + +export const LogoSvg: {[LogoEnum]: string} = { + Red: '', + Cyan: '' +} diff --git a/src/gui/nav/DrawerMenu.js b/src/gui/nav/DrawerMenu.js index 4d23cdfea8b3..87542afd5092 100644 --- a/src/gui/nav/DrawerMenu.js +++ b/src/gui/nav/DrawerMenu.js @@ -5,11 +5,14 @@ import {ButtonColors, ButtonN, ButtonType} from "../base/ButtonN" import {BootIcons} from "../base/icons/BootIcons" import {LogoutUrl} from "../base/Header" import {showUpgradeDialog, writeInviteMail, writeSupportMail} from "./NavFunctions" -import {isIOSApp} from "../../api/Env" +import {isDesktop, isIOSApp} from "../../api/Env" import {logins} from "../../api/main/LoginController" import {navButtonRoutes} from "../../misc/RouteChange" import {getSafeAreaInsetLeft} from "../HtmlUtils" import {isNewMailActionAvailable} from "../../mail/MailView" +import {Icons} from "../base/icons/Icons" +import {nativeApp} from "../../native/NativeWrapper" +import {Request} from "../../api/common/WorkerProtocol" type Attrs = void @@ -20,6 +23,15 @@ export class DrawerMenu implements MComponent { 'padding-left': getSafeAreaInsetLeft() }, }, m(".flex.col.height-100p.items-center.pt.pb", [ + isDesktop() + ? m(ButtonN, { + icon: () => Icons.NewWindow, + label: "openNewWindow_action", + click: () => nativeApp.invokeNative(new Request('openNewWindow', [])), + type: ButtonType.ActionLarge, + colors: ButtonColors.DrawerNav + }) + : null, m(".flex-grow"), !isIOSApp() && logins.getUserController().isFreeAccount() ? m(ButtonN, { @@ -63,7 +75,16 @@ export class DrawerMenu implements MComponent { click: () => m.route.set(LogoutUrl), type: ButtonType.ActionLarge, colors: ButtonColors.DrawerNav, - }) + }), + isDesktop() + ? m(ButtonN, { + icon: () => Icons.Power, + label: "quit_action", + click: () => nativeApp.invokeNative(new Request('closeApp', [])), + type: ButtonType.ActionLarge, + colors: ButtonColors.DrawerNav + }) + : null, ])) } } \ No newline at end of file diff --git a/src/misc/TranslationKey.js b/src/misc/TranslationKey.js index c411e7826e0c..253bf0c25f3e 100644 --- a/src/misc/TranslationKey.js +++ b/src/misc/TranslationKey.js @@ -1067,3 +1067,6 @@ export type TranslationKeyType = "about_label" | "yourFolders_action" | "yourMessage_label" | "emptyString_msg" + | "autoUpdate_label" + | "showTrayIcon_action" + | "mayNotWorkForAllDe_msg" diff --git a/src/settings/DesktopSettingsViewer.js b/src/settings/DesktopSettingsViewer.js index fd6aca0a095c..4591adcce0b1 100644 --- a/src/settings/DesktopSettingsViewer.js +++ b/src/settings/DesktopSettingsViewer.js @@ -26,8 +26,6 @@ const DownloadLocationStrategy = Object.freeze({ }) export class DesktopSettingsViewer implements UpdatableSettingsViewer { - view: Function; - _isDefaultMailtoHandler: Stream; _defaultDownloadPath: Stream; _runAsTrayApp: Stream; @@ -64,7 +62,8 @@ export class DesktopSettingsViewer implements UpdatableSettingsViewer { } const setRunAsTrayAppAttrs: DropDownSelectorAttrs = { - label: "runAsTrayApp_action", + label: env.platformId === 'linux' ? "showTrayIcon_action" : "runAsTrayApp_action", + helpLabel: env.platformId === 'linux' ? () => lang.get("mayNotWorkForAllDe_msg") : () => "", items: [ {name: lang.get("yes_label"), value: true}, {name: lang.get("no_label"), value: false} diff --git a/src/translations/en.js b/src/translations/en.js index ad0b41173a7e..d932ef9b1cbb 100644 --- a/src/translations/en.js +++ b/src/translations/en.js @@ -1079,6 +1079,8 @@ module.exports = { "yes_label": "Yes", "yourCalendars_label": "Your calendars", "yourFolders_action": "YOUR FOLDERS", - "yourMessage_label": "Your message" + "yourMessage_label": "Your message", + "showTrayIcon_action": "Show Tray Icon", + "mayNotWorkForAllDe_msg": "This may not work for all Linux desktop environments." } } diff --git a/test/client/desktop/ApplicationWindowTest.js b/test/client/desktop/ApplicationWindowTest.js index 2c07adfe41c9..c23f0c3d107d 100644 --- a/test/client/desktop/ApplicationWindowTest.js +++ b/test/client/desktop/ApplicationWindowTest.js @@ -186,6 +186,7 @@ o.spec("ApplicationWindow Test", () => { newWindow: () => { }, hide: () => {}, + minimize: () => {}, getIcon: () => 'this is a wm icon', recreateWindow: () => { } @@ -355,7 +356,7 @@ o.spec("ApplicationWindow Test", () => { ]) }) - o("shortcuts are used, linux", async function () { + o("shortcuts are used, linux & win", async function () { n.setPlatform('linux') const {electronMock, electronLocalshortcutMock, wmMock} = standardMocks() @@ -393,7 +394,7 @@ o.spec("ApplicationWindow Test", () => { o(bwInstance.loadURL.args[0]).equals('desktophtml') electronLocalshortcutMock.callbacks["Control+H"]() - o(wmMock.hide.callCount).equals(1) + o(wmMock.minimize.callCount).equals(1) electronLocalshortcutMock.callbacks["Control+N"]() o(wmMock.newWindow.callCount).equals(1) diff --git a/test/client/desktop/DesktopTrayTest.js b/test/client/desktop/DesktopTrayTest.js index be5a9c15b16c..4a526189ab50 100644 --- a/test/client/desktop/DesktopTrayTest.js +++ b/test/client/desktop/DesktopTrayTest.js @@ -29,8 +29,8 @@ o.spec("DesktopTrayTest", () => { app: { callbacks: {}, on: function (ev: string, cb: ()=>void) { - this.callbacks[ev] = cb - return n.spyify(electron.app) + this.callbacks[ev] = o.spy(cb) + return this }, dock: { show: () => { @@ -121,16 +121,17 @@ o.spec("DesktopTrayTest", () => { const electronMock = n.mock("electron", electron).set() //our modules - n.mock('../misc/LanguageViewModel', lang).set() + n.mock('../../misc/LanguageViewModel', lang).set() // instances const confMock = n.mock('__conf', conf).set() const notifierMock = n.mock('__notifier', notifier).set() const wmMock = n.mock('__wm', wm).set() - const {DesktopTray} = n.subject('../../src/desktop/DesktopTray.js') + const {DesktopTray} = n.subject('../../src/desktop/tray/DesktopTray.js') const tray = new DesktopTray(confMock, notifierMock) tray.setWindowManager(wmMock) + electronMock.app.callbacks["ready"]() tray.update() o(confMock.getDesktopConfig.callCount).equals(1) @@ -143,7 +144,7 @@ o.spec("DesktopTrayTest", () => { const electronMock = n.mock("electron", electron).set() //our modules - n.mock('../misc/LanguageViewModel', lang).set() + n.mock('../../misc/LanguageViewModel', lang).set() // instances const confMock = n.mock('__conf', conf) @@ -161,9 +162,10 @@ o.spec("DesktopTrayTest", () => { const notifierMock = n.mock('__notifier', notifier).set() const wmMock = n.mock('__wm', wm).set() - const {DesktopTray} = n.subject('../../src/desktop/DesktopTray.js') + const {DesktopTray} = n.subject('../../src/desktop/tray/DesktopTray.js') const tray = new DesktopTray(confMock, notifierMock) tray.setWindowManager(wmMock) + electronMock.app.callbacks["ready"]() tray.update() setTimeout(() => { @@ -185,7 +187,7 @@ o.spec("DesktopTrayTest", () => { const electronMock = n.mock("electron", electron).set() //our modules - n.mock('../misc/LanguageViewModel', lang).set() + n.mock('../../misc/LanguageViewModel', lang).set() // instances const confMock = n.mock('__conf', conf) @@ -203,9 +205,11 @@ o.spec("DesktopTrayTest", () => { const notifierMock = n.mock('__notifier', notifier).set() const wmMock = n.mock('__wm', wm).set() - const {DesktopTray} = n.subject('../../src/desktop/DesktopTray.js') + const {DesktopTray} = n.subject('../../src/desktop/tray/DesktopTray.js') const tray = new DesktopTray(confMock, notifierMock) tray.setWindowManager(wmMock) + console.log(electronMock.app.callbacks) + electronMock.app.callbacks["ready"]() tray.update() setTimeout(() => { @@ -221,7 +225,7 @@ o.spec("DesktopTrayTest", () => { const electronMock = n.mock("electron", electron).set() //our modules - n.mock('../misc/LanguageViewModel', lang).set() + n.mock('../../misc/LanguageViewModel', lang).set() // instances const confMock = n.mock('__conf', conf) @@ -239,9 +243,10 @@ o.spec("DesktopTrayTest", () => { const notifierMock = n.mock('__notifier', notifier).set() const wmMock = n.mock('__wm', wm).set() - const {DesktopTray} = n.subject('../../src/desktop/DesktopTray.js') + const {DesktopTray} = n.subject('../../src/desktop/tray/DesktopTray.js') const tray = new DesktopTray(confMock, notifierMock) tray.setWindowManager(wmMock) + electronMock.app.callbacks["ready"]() tray.update() setTimeout(() => { diff --git a/test/client/desktop/DesktopWindowManagerTest.js b/test/client/desktop/DesktopWindowManagerTest.js index 71c4487c97ed..76cd6c944f0f 100644 --- a/test/client/desktop/DesktopWindowManagerTest.js +++ b/test/client/desktop/DesktopWindowManagerTest.js @@ -2,7 +2,7 @@ import o from "ospec/ospec.js" import n from "../nodemocker" import type {UserInfo} from "../../../src/desktop/ApplicationWindow" -import {downcast, noOp} from "../../../src/api/common/utils/Utils" +import {noOp} from "../../../src/api/common/utils/Utils" o.spec("Desktop Window Manager Test", () => { n.startGroup({ @@ -159,7 +159,7 @@ o.spec("Desktop Window Manager Test", () => { // our modules const applicationWindowMock = n.mock("./ApplicationWindow", applicationWindow).set() const dlMock = n.mock("__dl", dl).set() - n.mock("./DesktopTray", desktopTray).set() + n.mock("./tray/DesktopTray", desktopTray).set() // instances const confMock = n.mock('__conf', conf).set() @@ -181,7 +181,7 @@ o.spec("Desktop Window Manager Test", () => { // our modules const applicationWindowMock = n.mock("./ApplicationWindow", applicationWindow).set() const dlMock = n.mock("__dl", dl).set() - n.mock("./DesktopTray", desktopTray).set() + n.mock("./tray/DesktopTray", desktopTray).set() // instances const confMock = n.mock('__conf', conf).set() @@ -214,7 +214,7 @@ o.spec("Desktop Window Manager Test", () => { // our modules const applicationWindowMock = n.mock("./ApplicationWindow", applicationWindow).set() const dlMock = n.mock("__dl", dl).set() - n.mock("./DesktopTray", desktopTray).set() + n.mock("./tray/DesktopTray", desktopTray).set() // instances const testBounds = {rect: {height: 10, width: 10, x: 10, y: 10}, fullscreen: false, scale: 1} @@ -266,7 +266,7 @@ o.spec("Desktop Window Manager Test", () => { // our modules const applicationWindowMock = n.mock("./ApplicationWindow", applicationWindow).set() const dlMock = n.mock("__dl", dl).set() - n.mock("./DesktopTray", desktopTray).set() + n.mock("./tray/DesktopTray", desktopTray).set() // instances const confMock = n.mock('__conf', conf).set() @@ -301,7 +301,7 @@ o.spec("Desktop Window Manager Test", () => { // our modules const applicationWindowMock = n.mock("./ApplicationWindow", applicationWindow).set() const dlMock = n.mock("__dl", dl).set() - n.mock("./DesktopTray", desktopTray).set() + n.mock("./tray/DesktopTray", desktopTray).set() // instances const confMock = n.mock('__conf', conf).set() @@ -343,7 +343,7 @@ o.spec("Desktop Window Manager Test", () => { // our modules const applicationWindowMock = n.mock("./ApplicationWindow", applicationWindow).set() const dlMock = n.mock("__dl", dl).set() - n.mock("./DesktopTray", desktopTray).set() + n.mock("./tray/DesktopTray", desktopTray).set() // instances const confMock = n.mock('__conf', conf).set() @@ -377,7 +377,7 @@ o.spec("Desktop Window Manager Test", () => { // our modules const applicationWindowMock = n.mock("./ApplicationWindow", applicationWindow).set() const dlMock = n.mock("__dl", dl).set() - n.mock("./DesktopTray", desktopTray).set() + n.mock("./tray/DesktopTray", desktopTray).set() // instances const confMock = n.mock('__conf', conf).set() @@ -457,46 +457,6 @@ o.spec("Desktop Window Manager Test", () => { }, 10) }) - o("retain logged in windows", () => { - // node modules - const electronMock = n.mock("electron", electron).set() - - // our modules - const applicationWindowMock = n.mock("./ApplicationWindow", applicationWindow).set() - const dlMock = n.mock("__dl", dl).set() - n.mock("./DesktopTray", desktopTray).set() - - // instances - const confMock = n.mock('__conf', conf).set() - const desktopTrayMock = n.mock('__tray', { - getIcon: () => "this is an icon", update: () => { - } - }).set() - const notifierMock = n.mock('__notifier', notifier).set() - const ipcMock = n.mock('__ipc', ipc).set() - - const {WindowManager} = n.subject('../../src/desktop/DesktopWindowManager.js') - const wm = new WindowManager(confMock, desktopTrayMock, notifierMock, dlMock) - wm.setIPC(ipcMock) - - const w = wm.newWindow(true) - w.callbacks['ready-to-show']() - const e = {preventDefault: o.spy()} - - // first, before before-quit - w.callbacks['close'](e) - o(w.hide.callCount).equals(1) - o(e.preventDefault.callCount).equals(1) - o(confMock.setDesktopConfig.callCount).equals(1) - - //now, after - downcast(electronMock.app.callbacks)['before-quit']() - w.callbacks['close'](e) - o(w.hide.callCount).equals(1) - o(e.preventDefault.callCount).equals(1) - o(confMock.setDesktopConfig.callCount).equals(2) - }) - o("hide() hides all windows", () => { // node modules const electronMock = n.mock("electron", electron).set() @@ -504,7 +464,7 @@ o.spec("Desktop Window Manager Test", () => { // our modules const applicationWindowMock = n.mock("./ApplicationWindow", applicationWindow).set() n.mock("__dl", dl).set() - n.mock("./DesktopTray", desktopTray).set() + n.mock("./tray/DesktopTray", desktopTray).set() // instances const confMock = n.mock('__conf', conf).set() diff --git a/test/client/desktop/ElectronUpdaterTest.js b/test/client/desktop/ElectronUpdaterTest.js index 177ad2ed3cc5..ab031b52d7e1 100644 --- a/test/client/desktop/ElectronUpdaterTest.js +++ b/test/client/desktop/ElectronUpdaterTest.js @@ -27,7 +27,7 @@ o.spec("ElectronUpdater Test", function (done, timeout) { app: { getPath: (path: string) => `/mock-${path}/`, getVersion: (): string => "3.45.0", - emit: ()=>{} + emit: () => {} } } @@ -40,43 +40,48 @@ o.spec("ElectronUpdater Test", function (done, timeout) { } const autoUpdater = { - callbacks: { - 'update-available': (any) => { - throw new Error('checkForUpdates called before setting listener') + autoUpdater: { + callbacks: { + 'update-available': (any) => { + throw new Error('checkForUpdates called before setting listener') + }, + 'update-not-available': (any) => { + throw new Error('checkForUpdates called before setting listener') + }, + 'update-downloaded': (any) => { + throw new Error('downloadUpdates called before setting listener') + }, + 'error': (any) => { + throw new Error('error called before setting error listener') + }, + 'checking-for-update': () => { + throw new Error('checking-for-updates called before setting listener') + } + }, + logger: undefined, + on: function (ev: string, cb: (any)=>void) { + this.callbacks[ev] = o.spy(cb) + return this }, - 'update-downloaded': (any) => { - throw new Error('downloadUpdates called before setting listener') + removeAllListeners: function (ev: string) { + this.callbacks[ev] = null + return this }, - 'error': (any) => { - throw new Error('error called before setting error listener') + checkForUpdates: function () { + setTimeout(() => this.callbacks['update-available']({ + sha512: 'sha512', + signature: 'signature', + }), 90) + return Promise.resolve() }, - 'checking-for-update': () => { - throw new Error('checking-for-updates called before setting listener') + downloadUpdate: function () { + setImmediate(() => this.callbacks['update-downloaded']({ + version: '4.5.0', + })) + return Promise.resolve() + }, + quitAndInstall: (isSilent: boolean, isForceRunAfter: boolean) => { } - }, - logger: undefined, - on: (ev: string, cb: (any)=>void) => { - autoUpdater.callbacks[ev] = cb - return n.spyify(autoUpdater) - }, - removeAllListeners: (ev: string) => { - autoUpdater.callbacks[ev] = null - return n.spyify(autoUpdater) - }, - checkForUpdates: () => { - setTimeout(() => autoUpdater.callbacks['update-available']({ - sha512: 'sha512', - signature: 'signature', - }), 90) - return Promise.resolve() - }, - downloadUpdate: () => { - setImmediate(() => autoUpdater.callbacks['update-downloaded']({ - version: '4.5.0', - })) - return Promise.resolve() - }, - quitAndInstall: (isSilent: boolean, isForceRunAfter: boolean) => { } } @@ -133,11 +138,11 @@ o.spec("ElectronUpdater Test", function (done, timeout) { o("update is available", done => { //mock node modules const forgeMock = n.mock('node-forge', nodeForge).set() - const autoUpdaterMock = n.mock('electron-updater', {autoUpdater}).set().autoUpdater + const autoUpdaterMock = n.mock('electron-updater', autoUpdater).set().autoUpdater const electronMock = n.mock('electron', electron).set() //mock our modules - n.mock('./DesktopTray', desktopTray).set() + n.mock('./tray/DesktopTray', desktopTray).set() n.mock('../misc/LanguageViewModel', lang).set() //mock instances @@ -193,7 +198,7 @@ o.spec("ElectronUpdater Test", function (done, timeout) { //mock node modules const forgeMock = n.mock('node-forge', nodeForge).set() const electronMock = n.mock('electron', electron).set() - const autoUpdaterMock = n.mock('electron-updater', {autoUpdater}) + const autoUpdaterMock = n.mock('electron-updater', autoUpdater) .with({ autoUpdater: { //never emit update-available @@ -203,7 +208,7 @@ o.spec("ElectronUpdater Test", function (done, timeout) { .set().autoUpdater //mock our modules - n.mock('./DesktopTray', desktopTray).set() + n.mock('./tray/DesktopTray', desktopTray).set() n.mock('../misc/LanguageViewModel', lang).set() //mock instances @@ -234,10 +239,10 @@ o.spec("ElectronUpdater Test", function (done, timeout) { //mock node modules const forgeMock = n.mock('node-forge', nodeForge).set() const electronMock = n.mock('electron', electron).set() - const autoUpdaterMock = n.mock('electron-updater', {autoUpdater}).set().autoUpdater + const autoUpdaterMock = n.mock('electron-updater', autoUpdater).set().autoUpdater //mock our modules - n.mock('./DesktopTray', desktopTray).set() + n.mock('./tray/DesktopTray', desktopTray).set() n.mock('../misc/LanguageViewModel', lang).set() //mock instances @@ -309,35 +314,35 @@ o.spec("ElectronUpdater Test", function (done, timeout) { //mock node modules const forgeMock = n.mock('node-forge', nodeForge).set() const electronMock = n.mock('electron', electron).set() - const autoUpdaterMock = n.mock('electron-updater', {autoUpdater}) + const autoUpdaterMock = n.mock('electron-updater', autoUpdater) .with({ autoUpdater: { - checkForUpdates: () => { - setTimeout(() => autoUpdater.callbacks['update-available']({ + checkForUpdates: function () { + setTimeout(() => this.callbacks['update-available']({ sha512: 'sha512', signature: 'signature', }), 90) return Promise.resolve() }, - downloadUpdate: () => { - setTimeout(() => autoUpdater.callbacks['update-downloaded']({ + downloadUpdate: function () { + setTimeout(() => this.callbacks['update-downloaded']({ version: '4.5.0', }), 30) return Promise.resolve() }, - on: (ev: string, cb: (e: {message: string})=>void) => { - autoUpdater.callbacks[ev] = cb + on: function (ev: string, cb: (e: {message: string})=>void) { + this.callbacks[ev] = cb if (ev === "error") { setTimeout(() => cb({message: "this is an autoUpdater error"}), 20) } - return autoUpdaterMock + return this } } }) .set().autoUpdater //mock our modules - n.mock('./DesktopTray', desktopTray).set() + n.mock('./tray/DesktopTray', desktopTray).set() n.mock('../misc/LanguageViewModel', lang).set() //mock instances @@ -380,13 +385,13 @@ o.spec("ElectronUpdater Test", function (done, timeout) { //mock node modules const forgeMock = n.mock('node-forge', nodeForge).set() const electronMock = n.mock('electron', electron).set() - const autoUpdaterMock = n.mock('electron-updater', {autoUpdater}) + const autoUpdaterMock = n.mock('electron-updater', autoUpdater) .with({ autoUpdater: { - downloadUpdate: () => { + downloadUpdate: function () { setImmediate(() => { try { - autoUpdater.callbacks['error']({message: "this is an autoUpdater error"}) + autoUpdaterMock.callbacks['error']({message: "this is an autoUpdater error"}) } catch (e) { // prevent escalation from killing the test suite console.log("caught") threw = true @@ -399,7 +404,7 @@ o.spec("ElectronUpdater Test", function (done, timeout) { .set().autoUpdater //mock our modules - n.mock('./DesktopTray', desktopTray).set() + n.mock('./tray/DesktopTray', desktopTray).set() n.mock('../misc/LanguageViewModel', lang).set() //mock instances @@ -416,7 +421,7 @@ o.spec("ElectronUpdater Test", function (done, timeout) { o(autoUpdaterMock.removeAllListeners.callCount).equals(4) o(threw).equals(true) done() - }, RETRY_INTERVAL * (MAX_NUM_ERRORS + 1)) + }, RETRY_INTERVAL * (MAX_NUM_ERRORS * 2)) }) o("works if second key is right one", done => { @@ -425,11 +430,11 @@ o.spec("ElectronUpdater Test", function (done, timeout) { const forgeMock = n.mock('node-forge', nodeForge).with({ publicKeyFromPem: (pem: string) => n.spyify(pem === "no" ? rightKey : wrongKey) }).set() - const autoUpdaterMock = n.mock('electron-updater', {autoUpdater}).set().autoUpdater + const autoUpdaterMock = n.mock('electron-updater', autoUpdater).set().autoUpdater const electronMock = n.mock('electron', electron).set() //mock our modules - n.mock('./DesktopTray', desktopTray).set() + n.mock('./tray/DesktopTray', desktopTray).set() n.mock('../misc/LanguageViewModel', lang).set() //mock instances diff --git a/test/client/desktop/IPCTest.js b/test/client/desktop/IPCTest.js index 1525b35203b2..7f7934964309 100644 --- a/test/client/desktop/IPCTest.js +++ b/test/client/desktop/IPCTest.js @@ -38,6 +38,9 @@ o.spec("IPC tests", () => { return this } }, + app: { + quit: () => {}, + }, dialog: { showOpenDialog: (e, opts) => Promise.resolve({filePaths: ["a", "list", "of", "paths"]}) }, @@ -763,6 +766,21 @@ o.spec("IPC tests", () => { }, 10) }) + o("closeApp", done => { + const {electronMock} = setUpWithWindowAndInit() + + electronMock.ipcMain.callbacks["42"]({}, JSON.stringify({ + type: "closeApp", + id: "id2", + args: [] + })) + + setTimeout(() => { + o(electronMock.app.quit.callCount).equals(1) + done() + }, 10) + }) + o("open", done => { const {electronMock, dlMock} = setUpWithWindowAndInit()