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..aadcaa981d 100644
--- a/package.json
+++ b/package.json
@@ -124,6 +124,9 @@
"rpm",
"AppImage"
],
+ "asarUnpack": [
+ "**/node_modules/sharp/**"
+ ],
"extraResources": [
{
"from": "binaries/client/linux/${arch}/kubectl",
@@ -260,6 +263,7 @@
"rfc6902": "^4.0.2",
"selfsigned": "^2.0.1",
"semver": "^7.3.7",
+ "sharp": "^0.30.3",
"shell-env": "^3.0.1",
"spdy": "^4.0.2",
"tar": "^6.1.11",
@@ -388,7 +392,6 @@
"react-window": "^1.8.6",
"sass": "^1.49.11",
"sass-loader": "^12.6.0",
- "sharp": "^0.30.3",
"style-loader": "^3.3.1",
"tailwindcss": "^3.0.23",
"tar-stream": "^2.2.0",
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..af129ac27a 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 type { Disposer } from "../../common/utils";
+import { base64, disposer, getOrInsertWithAsync, toJS } from "../../common/utils";
import type { TrayMenuRegistration } from "./tray-menu-registration";
+import sharp from "sharp";
+import LogoLens from "../../renderer/components/icon/logo-lens.svg";
+import { JSDOM } from "jsdom";
const TRAY_LOG_PREFIX = "[TRAY]";
@@ -23,25 +27,69 @@ 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 CreateTrayIconArgs {
+ shouldUseDarkColors: boolean;
+ size: number;
+ sourceSvg: string;
}
-export function initTray(
+const trayIcons = new Map();
+
+async function createTrayIcon({ shouldUseDarkColors, size, sourceSvg }: CreateTrayIconArgs): Promise {
+ return getOrInsertWithAsync(trayIcons, shouldUseDarkColors, async () => {
+ const trayIconColor = shouldUseDarkColors ? "white" : "black"; // Invert to show contrast
+ const parsedSvg = base64.decode(sourceSvg.split("base64,")[1]);
+ const svgDom = new JSDOM(`${parsedSvg}`);
+ 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 createCurrentTrayIcon() {
+ return createTrayIcon({
+ shouldUseDarkColors: nativeTheme.shouldUseDarkColors,
+ size: 16,
+ sourceSvg: LogoLens,
+ });
+}
+
+function watchShouldUseDarkColors(tray: Tray): Disposer {
+ let prevShouldUseDarkColors = nativeTheme.shouldUseDarkColors;
+ const onUpdated = () => {
+ if (prevShouldUseDarkColors !== nativeTheme.shouldUseDarkColors) {
+ prevShouldUseDarkColors = nativeTheme.shouldUseDarkColors;
+ createCurrentTrayIcon()
+ .then(img => tray.setImage(img));
+ }
+ };
+
+ nativeTheme.on("updated", onUpdated);
+
+ return () => nativeTheme.off("updated", onUpdated);
+}
+
+export async function initTray(
windowManager: WindowManager,
trayMenuItems: IComputedValue,
navigateToPreferences: () => void,
-) {
- const icon = getTrayIcon();
+): Promise {
+ const icon = await createCurrentTrayIcon();
+ const dispose = disposer();
tray = new Tray(icon);
tray.setToolTip(packageInfo.description);
tray.setIgnoreDoubleClickEvents(true);
+ dispose.push(watchShouldUseDarkColors(tray));
+
if (isWindows) {
tray.on("click", () => {
windowManager
@@ -50,7 +98,7 @@ export function initTray(
});
}
- const disposers = [
+ dispose.push(
autorun(() => {
try {
const menu = createTrayMenu(windowManager, toJS(trayMenuItems.get()), navigateToPreferences);
@@ -60,13 +108,13 @@ export function initTray(
logger.error(`${TRAY_LOG_PREFIX}: building failed`, { error });
}
}),
- ];
+ () => {
+ tray?.destroy();
+ tray = null;
+ },
+ );
- return () => {
- disposers.forEach(disposer => disposer());
- tray?.destroy();
- tray = null;
- };
+ return dispose;
}
function getMenuItemConstructorOptions(trayItem: TrayMenuRegistration): Electron.MenuItemConstructorOptions {