diff --git a/build/build_tray_icon.ts b/build/build_tray_icon.ts deleted file mode 100644 index 0850011815..0000000000 --- a/build/build_tray_icon.ts +++ /dev/null @@ -1,55 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ -import path from "path"; -import sharp from "sharp"; -import jsdom from "jsdom"; -import fs from "fs-extra"; - -export async function generateTrayIcon( - { - outputFilename = "trayIcon", - svgIconPath = path.resolve(__dirname, "../src/renderer/components/icon/logo-lens.svg"), - outputFolder = path.resolve(__dirname, "./tray"), - dpiSuffix = "2x", - pixelSize = 32, - shouldUseDarkColors = false, // managed by electron.nativeTheme.shouldUseDarkColors - } = {}) { - outputFilename += `${shouldUseDarkColors ? "Dark" : ""}Template`; // e.g. output trayIconDarkTemplate@2x.png - dpiSuffix = dpiSuffix !== "1x" ? `@${dpiSuffix}` : ""; - const pngIconDestPath = path.resolve(outputFolder, `${outputFilename}${dpiSuffix}.png`); - - try { - // Modify .SVG colors - const trayIconColor = shouldUseDarkColors ? "black" : "white"; - const svgDom = await jsdom.JSDOM.fromFile(svgIconPath); - const svgRoot = svgDom.window.document.body.getElementsByTagName("svg")[0]; - - svgRoot.innerHTML += ``; - const svgIconBuffer = Buffer.from(svgRoot.outerHTML); - // Resize and convert to .PNG - const pngIconBuffer: Buffer = await sharp(svgIconBuffer) - .resize({ width: pixelSize, height: pixelSize }) - .png() - .toBuffer(); - - // Save icon - await fs.writeFile(pngIconDestPath, pngIconBuffer); - console.info(`[DONE]: Tray icon saved at "${pngIconDestPath}"`); - } catch (err) { - console.error(`[ERROR]: ${err}`); - } -} - -// Run -const iconSizes: Record = { - "1x": 16, - "2x": 32, - "3x": 48, -}; - -Object.entries(iconSizes).forEach(([dpiSuffix, pixelSize]) => { - generateTrayIcon({ dpiSuffix, pixelSize, shouldUseDarkColors: false }); - generateTrayIcon({ dpiSuffix, pixelSize, shouldUseDarkColors: true }); -}); diff --git a/build/tray/trayIconDarkTemplate.png b/build/tray/trayIconDarkTemplate.png deleted file mode 100644 index d0a4bdce20..0000000000 Binary files a/build/tray/trayIconDarkTemplate.png and /dev/null differ diff --git a/build/tray/trayIconDarkTemplate@2x.png b/build/tray/trayIconDarkTemplate@2x.png deleted file mode 100644 index 46615aa5bf..0000000000 Binary files a/build/tray/trayIconDarkTemplate@2x.png and /dev/null differ diff --git a/build/tray/trayIconDarkTemplate@3x.png b/build/tray/trayIconDarkTemplate@3x.png deleted file mode 100644 index 0c018f3ed3..0000000000 Binary files a/build/tray/trayIconDarkTemplate@3x.png and /dev/null differ diff --git a/build/tray/trayIconTemplate.png b/build/tray/trayIconTemplate.png deleted file mode 100644 index 16ada5ad31..0000000000 Binary files a/build/tray/trayIconTemplate.png and /dev/null differ diff --git a/build/tray/trayIconTemplate@2x.png b/build/tray/trayIconTemplate@2x.png deleted file mode 100644 index 73c40de994..0000000000 Binary files a/build/tray/trayIconTemplate@2x.png and /dev/null differ diff --git a/build/tray/trayIconTemplate@3x.png b/build/tray/trayIconTemplate@3x.png deleted file mode 100644 index d7a39b9260..0000000000 Binary files a/build/tray/trayIconTemplate@3x.png and /dev/null differ diff --git a/package.json b/package.json index 6be5a730ab..211d2b909f 100644 --- a/package.json +++ b/package.json @@ -209,6 +209,7 @@ "@sentry/electron": "^3.0.6", "@sentry/integrations": "^6.19.3", "@types/circular-dependency-plugin": "5.0.5", + "@types/data-urls": "^2.0.1", "abort-controller": "^3.0.0", "auto-bind": "^4.0.0", "await-lock": "^2.1.0", @@ -216,6 +217,7 @@ "chokidar": "^3.5.3", "conf": "^7.1.2", "crypto-js": "^4.1.1", + "data-urls": "^3.0.1", "electron-devtools-installer": "^3.2.0", "electron-updater": "^4.6.5", "electron-window-state": "^5.0.3", diff --git a/src/common/utils/collection-functions.ts b/src/common/utils/collection-functions.ts index 0329a52379..76646601eb 100644 --- a/src/common/utils/collection-functions.ts +++ b/src/common/utils/collection-functions.ts @@ -48,6 +48,17 @@ export function getOrInsertWith(map: Map, key: K, builder: () => V): return map.get(key); } +/** + * Like {@link getOrInsertWith} but the builder is async and will be awaited before inserting into the map + */ +export async function getOrInsertWithAsync(map: Map, key: K, asyncBuilder: () => Promise): Promise { + if (!map.has(key)) { + map.set(key, await asyncBuilder()); + } + + return map.get(key); +} + /** * Set the value associated with `key` iff there was not a previous value * @param map The map to interact with diff --git a/src/main/index.ts b/src/main/index.ts index f6c0b1bbc0..5b4f965a93 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -305,7 +305,7 @@ async function main(di: DiContainer) { onQuitCleanup.push( initMenu(applicationMenuItems), - initTray(windowManager, trayMenuItems, navigateToPreferences), + await initTray(windowManager, trayMenuItems, navigateToPreferences), () => ShellSession.cleanup(), ); diff --git a/src/main/tray/tray.ts b/src/main/tray/tray.ts index bef95610fd..fc0ca1ec45 100644 --- a/src/main/tray/tray.ts +++ b/src/main/tray/tray.ts @@ -3,19 +3,23 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import path from "path"; import packageInfo from "../../../package.json"; -import { Menu, Tray } from "electron"; +import type { NativeImage } from "electron"; +import { Menu, nativeImage, nativeTheme, Tray } from "electron"; import type { IComputedValue } from "mobx"; import { autorun } from "mobx"; import { showAbout } from "../menu/menu"; import { checkForUpdates, isAutoUpdateEnabled } from "../app-updater"; import type { WindowManager } from "../window-manager"; import logger from "../logger"; -import { isDevelopment, isWindows, productName, staticFilesDirectory } from "../../common/vars"; +import { isWindows, productName } from "../../common/vars"; import { exitApp } from "../exit-app"; -import { toJS } from "../../common/utils"; +import { getOrInsertWithAsync, toJS } from "../../common/utils"; import type { TrayMenuRegistration } from "./tray-menu-registration"; +import parseDataURL from "data-urls"; +import sharp from "sharp"; +import LogoLens from "../../renderer/components/icon/logo-lens.svg"; +import { JSDOM } from "jsdom"; const TRAY_LOG_PREFIX = "[TRAY]"; @@ -23,24 +27,64 @@ const TRAY_LOG_PREFIX = "[TRAY]"; // note: instance of Tray should be saved somewhere, otherwise it disappears export let tray: Tray; -export function getTrayIcon(): string { - return path.resolve( - staticFilesDirectory, - isDevelopment ? "../build/tray" : "icons", // copied within electron-builder extras - "trayIconTemplate.png", - ); +interface ComputeTrayIconArgs { + shouldUseDarkColors: boolean; + size: number; + sourceSvg: string; } -export function initTray( +const trayIcons = new Map(); + +async function computeTrayIcon({ shouldUseDarkColors, size, sourceSvg }: ComputeTrayIconArgs): Promise { + return getOrInsertWithAsync(trayIcons, shouldUseDarkColors, async () => { + const trayIconColor = shouldUseDarkColors ? "white" : "black"; // Invert to show contrast + const parsedSvg = parseDataURL(sourceSvg); + const svgContent = Buffer.from(parsedSvg.body.buffer).toString(); + const svgDom = new JSDOM(`${svgContent}`); + const svgRoot = svgDom.window.document.body.getElementsByTagName("svg")[0]; + + svgRoot.innerHTML += ``; + + const iconBuffer = await sharp(Buffer.from(svgRoot.outerHTML)) + .resize({ width: size, height: size }) + .png() + .toBuffer(); + + return nativeImage.createFromBuffer(iconBuffer); + }); +} + +function computeCurrentTrayIcon() { + return computeTrayIcon({ + shouldUseDarkColors: nativeTheme.shouldUseDarkColors, + size: 16, + sourceSvg: LogoLens, + }); +} + +function watchShouldUseDarkColors(tray: Tray) { + let prevShouldUseDarkColors = nativeTheme.shouldUseDarkColors; + + nativeTheme.on("updated", () => { + if (prevShouldUseDarkColors !== nativeTheme.shouldUseDarkColors) { + prevShouldUseDarkColors = nativeTheme.shouldUseDarkColors; + computeCurrentTrayIcon() + .then(img => tray.setImage(img)); + } + }); +} + +export async function initTray( windowManager: WindowManager, trayMenuItems: IComputedValue, navigateToPreferences: () => void, ) { - const icon = getTrayIcon(); + const icon = await computeCurrentTrayIcon(); tray = new Tray(icon); tray.setToolTip(packageInfo.description); tray.setIgnoreDoubleClickEvents(true); + watchShouldUseDarkColors(tray); if (isWindows) { tray.on("click", () => { diff --git a/yarn.lock b/yarn.lock index 2fd51e9d1a..d5ba109fcf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1470,6 +1470,15 @@ resolved "https://registry.yarnpkg.com/@types/crypto-js/-/crypto-js-3.1.47.tgz#36e549dd3f1322742a3a738e7c113ebe48221860" integrity sha512-eI6gvpcGHLk3dAuHYnRCAjX+41gMv1nz/VP55wAe5HtmAKDOoPSfr3f6vkMc08ov1S0NsjvUBxDtHHxqQY1LGA== +"@types/data-urls@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@types/data-urls/-/data-urls-2.0.1.tgz#4a5491583513e8a84044f7e7fbac2ca246c44ed9" + integrity sha512-8/IGbezKc9SEeL9BHdZ4whS9EaYzXfQ6eEC2s34RXfnaEkTasKCWldq/gfoEzbByDD5GXyqEESogVRtdskJXEA== + dependencies: + "@types/node" "*" + "@types/whatwg-mimetype" "*" + "@types/whatwg-url" "*" + "@types/debug@^4.1.6": version "4.1.7" resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.7.tgz#7cc0ea761509124709b8b2d1090d8f6c17aadb82" @@ -2118,6 +2127,11 @@ resolved "https://registry.yarnpkg.com/@types/verror/-/verror-1.10.5.tgz#2a1413aded46e67a1fe2386800e291123ed75eb1" integrity sha512-9UjMCHK5GPgQRoNbqdLIAvAy0EInuiqbW0PBMtVP6B5B2HQJlvoJHM+KodPZMEjOa5VkSc+5LH7xy+cUzQdmHw== +"@types/webidl-conversions@*": + version "6.1.1" + resolved "https://registry.yarnpkg.com/@types/webidl-conversions/-/webidl-conversions-6.1.1.tgz#e33bc8ea812a01f63f90481c666334844b12a09e" + integrity sha512-XAahCdThVuCFDQLT7R7Pk/vqeObFNL3YqRyFZg+AqAP/W1/w3xHaIxuW7WszQqTbIBOPRcItYJIou3i/mppu3Q== + "@types/webpack-dev-server@^4.7.2": version "4.7.2" resolved "https://registry.yarnpkg.com/@types/webpack-dev-server/-/webpack-dev-server-4.7.2.tgz#a12d9881aa23cdd4cecbb2d31fa784a45c4967e0" @@ -2168,6 +2182,19 @@ tapable "^2.2.0" webpack "^5" +"@types/whatwg-mimetype@*": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@types/whatwg-mimetype/-/whatwg-mimetype-2.1.1.tgz#1b7b7aecaa3695209fd2f3a808dd5729973a52fa" + integrity sha512-ojnf89qt5AWnqsjyPqMLN8uVaxm5x23vxNQ1me6EPBOdJe1YYuIZUzg809MZUG8UU6HKhkr6ah4fi2WUvD0DFw== + +"@types/whatwg-url@*": + version "8.2.1" + resolved "https://registry.yarnpkg.com/@types/whatwg-url/-/whatwg-url-8.2.1.tgz#f1aac222dab7c59e011663a0cb0a3117b2ef05d4" + integrity sha512-2YubE1sjj5ifxievI5Ge1sckb9k/Er66HyR2c+3+I6VDUUg1TLPdYYTEbQ+DjRkS4nTxMJhgWfSfMRD2sl2EYQ== + dependencies: + "@types/node" "*" + "@types/webidl-conversions" "*" + "@types/ws@^6.0.1": version "6.0.4" resolved "https://registry.yarnpkg.com/@types/ws/-/ws-6.0.4.tgz#7797707c8acce8f76d8c34b370d4645b70421ff1" @@ -4412,6 +4439,15 @@ data-urls@^2.0.0: whatwg-mimetype "^2.3.0" whatwg-url "^8.0.0" +data-urls@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-3.0.1.tgz#597fc2ae30f8bc4dbcf731fcd1b1954353afc6f8" + integrity sha512-Ds554NeT5Gennfoo9KN50Vh6tpgtvYEwraYjejXnyTpu1C7oXKxdFk75REooENHE8ndTVOJuv+BEs4/J/xcozw== + dependencies: + abab "^2.0.3" + whatwg-mimetype "^3.0.0" + whatwg-url "^10.0.0" + date-fns@^2.16.1: version "2.28.0" resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.28.0.tgz#9570d656f5fc13143e50c975a3b6bbeb46cd08b2" @@ -12967,6 +13003,13 @@ tr46@^2.1.0: dependencies: punycode "^2.1.1" +tr46@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-3.0.0.tgz#555c4e297a950617e8eeddef633c87d4d9d6cbf9" + integrity sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA== + dependencies: + punycode "^2.1.1" + tr46@~0.0.3: version "0.0.3" resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" @@ -13585,6 +13628,11 @@ webidl-conversions@^6.1.0: resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-6.1.0.tgz#9111b4d7ea80acd40f5270d666621afa78b69514" integrity sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w== +webidl-conversions@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-7.0.0.tgz#256b4e1882be7debbf01d05f0aa2039778ea080a" + integrity sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g== + webpack-cli@^4.9.2: version "4.9.2" resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-4.9.2.tgz#77c1adaea020c3f9e2db8aad8ea78d235c83659d" @@ -13731,6 +13779,19 @@ whatwg-mimetype@^2.3.0: resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf" integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g== +whatwg-mimetype@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz#5fa1a7623867ff1af6ca3dc72ad6b8a4208beba7" + integrity sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q== + +whatwg-url@^10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-10.0.0.tgz#37264f720b575b4a311bd4094ed8c760caaa05da" + integrity sha512-CLxxCmdUby142H5FZzn4D8ikO1cmypvXVQktsgosNy4a4BHrDHeciBBGZhb0bNoR5/MltoCatso+vFjjGx8t0w== + dependencies: + tr46 "^3.0.0" + webidl-conversions "^7.0.0" + whatwg-url@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d"