diff --git a/src/main/appBadge.ts b/src/main/appBadge.ts index 46abe1d..4b32fe9 100644 --- a/src/main/appBadge.ts +++ b/src/main/appBadge.ts @@ -6,22 +6,27 @@ import { app, NativeImage, nativeImage } from "electron"; import { join } from "path"; +import { IpcEvents } from "shared/IpcEvents"; import { BADGE_DIR } from "shared/paths"; +import { mainWin } from "./mainWindow"; + const imgCache = new Map(); -function loadBadge(index: number) { +export function loadBadge(index: number) { const cached = imgCache.get(index); if (cached) return cached; - const img = nativeImage.createFromPath(join(BADGE_DIR, `${index}.ico`)); + const img = nativeImage.createFromPath(join(BADGE_DIR, `${index}.png`)); imgCache.set(index, img); return img; } -let lastIndex: null | number = -1; +let lastBadgeIndex: null | number = -1; +export var lastBadgeCount: number = -1; export function setBadgeCount(count: number) { + lastBadgeCount = count; switch (process.platform) { case "linux": if (count === -1) count = 0; @@ -36,15 +41,17 @@ export function setBadgeCount(count: number) { break; case "win32": const [index, description] = getBadgeIndexAndDescription(count); - if (lastIndex === index) break; + if (lastBadgeIndex === index) break; - lastIndex = index; + lastBadgeIndex = index; // circular import shenanigans const { mainWin } = require("./mainWindow") as typeof import("./mainWindow"); mainWin.setOverlayIcon(index === null ? null : loadBadge(index), description); break; } + + mainWin.webContents.send(IpcEvents.SET_CURRENT_VOICE_TRAY_ICON); } function getBadgeIndexAndDescription(count: number): [number | null, string] { diff --git a/src/main/ipc.ts b/src/main/ipc.ts index bfe9208..126895a 100644 --- a/src/main/ipc.ts +++ b/src/main/ipc.ts @@ -28,9 +28,17 @@ import { IpcEvents } from "../shared/IpcEvents"; import { setBadgeCount } from "./appBadge"; import { autoStart } from "./autoStart"; import { VENCORD_FILES_DIR, VENCORD_QUICKCSS_FILE, VENCORD_THEMES_DIR } from "./constants"; -import { mainWin, setTrayIcon } from "./mainWindow"; +import { mainWin } from "./mainWindow"; import { Settings } from "./settings"; -import { createTrayIcon, generateTrayIcons, getTrayIconFile, getTrayIconFileSync, pickTrayIcon } from "./tray"; +import { + createTrayIcon, + generateTrayIcons, + getIconWithBadge, + getTrayIconFile, + getTrayIconFileSync, + pickTrayIcon, + setTrayIcon +} from "./tray"; import { handle, handleSync } from "./utils/ipcWrappers"; import { PopoutWindows } from "./utils/popout"; import { isDeckGameMode, showGamePage } from "./utils/steamOS"; @@ -170,3 +178,4 @@ handle(IpcEvents.CREATE_TRAY_ICON_RESPONSE, (_, iconName, dataURL, isCustomIcon) ); handle(IpcEvents.GENERATE_TRAY_ICONS, () => generateTrayIcons()); handle(IpcEvents.SELECT_TRAY_ICON, async (_, iconName) => pickTrayIcon(iconName)); +handle(IpcEvents.GET_ICON_WITH_BADGE, async (_, dataURL) => getIconWithBadge(dataURL)); diff --git a/src/main/mainWindow.ts b/src/main/mainWindow.ts index cd60521..f27cf9b 100755 --- a/src/main/mainWindow.ts +++ b/src/main/mainWindow.ts @@ -11,8 +11,6 @@ import { dialog, Menu, MenuItemConstructorOptions, - NativeImage, - nativeImage, nativeTheme, screen, session, @@ -21,7 +19,7 @@ import { import { rm } from "fs/promises"; import { join } from "path"; import { IpcEvents } from "shared/IpcEvents"; -import { ICON_PATH, ICONS_DIR } from "shared/paths"; +import { ICON_PATH } from "shared/paths"; import { isTruthy } from "shared/utils/guards"; import { once } from "shared/utils/once"; import type { SettingsStore } from "shared/utils/SettingsStore"; @@ -40,13 +38,13 @@ import { } from "./constants"; import { Settings, State, VencordSettings } from "./settings"; import { createSplashWindow } from "./splash"; -import { generateTrayIcons, isCustomIcon, statusToSettingsKey } from "./tray"; +import { setTrayIcon } from "./tray"; import { makeLinksOpenExternally } from "./utils/makeLinksOpenExternally"; import { applyDeckKeyboardFix, askToApplySteamLayout, isDeckGameMode } from "./utils/steamOS"; import { downloadVencordFiles, ensureVencordFiles } from "./utils/vencordLoader"; let isQuitting = false; -let tray: Tray; +export let tray: Tray; applyDeckKeyboardFix(); @@ -127,14 +125,7 @@ function initTray(win: BrowserWindow) { ]); tray = new Tray(ICON_PATH); - try { - if (Settings.store.trayMainOverride) tray.setImage(join(ICONS_DIR, "icon_custom.png")); - else tray.setImage(join(ICONS_DIR, "icon.png")); - } catch (error) { - console.log("Error while loading custom tray image. Recreating new ones."); - Settings.store.trayMainOverride = false; - generateTrayIcons(); - } + setTrayIcon("icon"); tray.setToolTip("Vesktop"); tray.setContextMenu(trayMenu); tray.on("click", onTrayClick); @@ -512,34 +503,3 @@ export async function createWindows() { initArRPC(); } - -export async function setTrayIcon(iconName: string) { - if (!tray || tray.isDestroyed()) return; - const Icons = new Set(["speaking", "muted", "deafened", "idle", "icon"]); - - if (!Icons.has(iconName)) return; - try { - var trayImage: NativeImage; - if (isCustomIcon(iconName)) { - trayImage = nativeImage.createFromPath(join(ICONS_DIR, iconName + "_custom.png")); - if (trayImage.isEmpty()) { - const iconKey = statusToSettingsKey[iconName as keyof typeof statusToSettingsKey]; - Settings.store[iconKey] = false; - generateTrayIcons(); - return; - } - } else trayImage = nativeImage.createFromPath(join(ICONS_DIR, iconName + ".png")); - if (trayImage.isEmpty()) { - generateTrayIcons(); - return; - } - if (process.platform === "darwin") { - trayImage = trayImage.resize({ width: 16, height: 16 }); - } - tray.setImage(trayImage); - } catch (error) { - console.log("Error: ", error, "Regenerating tray icons."); - generateTrayIcons(); // TODO: pass here only one icon request - } - return; -} diff --git a/src/main/tray.ts b/src/main/tray.ts index 0525ec3..cdec95d 100644 --- a/src/main/tray.ts +++ b/src/main/tray.ts @@ -5,13 +5,14 @@ */ import { dialog, NativeImage, nativeImage } from "electron"; -import { copyFileSync, mkdirSync, writeFileSync } from "fs"; +import { mkdirSync, writeFileSync } from "fs"; import { readFile } from "fs/promises"; import { join } from "path"; import { IpcEvents } from "shared/IpcEvents"; import { ICONS_DIR, STATIC_DIR } from "shared/paths"; -import { mainWin } from "./mainWindow"; +import { lastBadgeCount, loadBadge } from "./appBadge"; +import { mainWin, tray } from "./mainWindow"; import { Settings } from "./settings"; export const statusToSettingsKey = { @@ -27,6 +28,62 @@ export const isCustomIcon = (status: string) => { return Settings.store[settingKey]; }; +export async function setTrayIcon(iconName: string) { + if (!tray || tray.isDestroyed()) return; + const Icons = new Set(["speaking", "muted", "deafened", "idle", "icon"]); + if (!Icons.has(iconName)) return; + + // if need to set main icon then check whether there is need of notif badge + if (iconName === "icon" && lastBadgeCount && lastBadgeCount > 0) { + var trayImage: NativeImage; + if (isCustomIcon("icon")) { + console.log("setting badge and CUSTOM icon"); + trayImage = nativeImage.createFromPath(join(ICONS_DIR, "icon_custom.png")); + } else { + console.log("setting badge and stock icon"); + trayImage = nativeImage.createFromPath(join(ICONS_DIR, "icon.png")); + } + + const badgeImg = loadBadge(lastBadgeCount); + // and send IPC call to renderer to add to image + mainWin.webContents.send(IpcEvents.ADD_BADGE_TO_ICON, trayImage.toDataURL(), badgeImg.toDataURL()); + return; + } + + try { + var trayImage: NativeImage; + if (isCustomIcon(iconName)) { + trayImage = nativeImage.createFromPath(join(ICONS_DIR, iconName + "_custom.png")); + if (trayImage.isEmpty()) { + const iconKey = statusToSettingsKey[iconName as keyof typeof statusToSettingsKey]; + Settings.store[iconKey] = false; + generateTrayIcons(); + return; + } + } else trayImage = nativeImage.createFromPath(join(ICONS_DIR, iconName + ".png")); + if (trayImage.isEmpty()) { + generateTrayIcons(); + return; + } + if (process.platform === "darwin") { + trayImage = trayImage.resize({ width: 16, height: 16 }); + } + tray.setImage(trayImage); + } catch (error) { + console.log("Error: ", error, "Regenerating tray icons."); + generateTrayIcons(); // TODO: pass here only one icon request + } + return; +} + +export async function setTrayIconWithBadge(iconDataURL: string) { + var trayImage = nativeImage.createFromDataURL(iconDataURL); + if (process.platform === "darwin") { + trayImage = trayImage.resize({ width: 16, height: 16 }); + } + tray.setImage(trayImage); +} + export async function getTrayIconFile(iconName: string) { const Icons = new Set(["speaking", "muted", "deafened", "idle"]); if (!Icons.has(iconName)) { @@ -62,7 +119,9 @@ export async function createTrayIcon(iconName: string, iconDataURL: string, isCu // primarily called from renderer using CREATE_TRAY_ICON_RESPONSE IPC call iconDataURL = iconDataURL.replace(/^data:image\/png;base64,/, ""); if (isCustomIcon) { - writeFileSync(join(ICONS_DIR, iconName + "_custom.png"), iconDataURL, "base64"); + const img = nativeImage.createFromDataURL(iconDataURL).resize({ width: 128, height: 128 }); + // writeFileSync(join(ICONS_DIR, iconName + "_custom.png"), img.toDataURL(), "base64"); + writeFileSync(join(ICONS_DIR, iconName + "_custom.png"), img.toPNG()); } else { writeFileSync(join(ICONS_DIR, iconName + ".png"), iconDataURL, "base64"); } @@ -76,7 +135,8 @@ export async function generateTrayIcons() { for (const icon of Icons) { mainWin.webContents.send(IpcEvents.CREATE_TRAY_ICON_REQUEST, icon); } - copyFileSync(join(STATIC_DIR, "icon.png"), join(ICONS_DIR, "icon.png")); + const img = nativeImage.createFromPath(join(STATIC_DIR, "icon.png")).resize({ width: 128, height: 128 }); + writeFileSync(join(ICONS_DIR, "icon.png"), img.toPNG()); mainWin.webContents.send(IpcEvents.SET_CURRENT_VOICE_TRAY_ICON); } @@ -93,6 +153,11 @@ export async function pickTrayIcon(iconName: string) { // add .svg !! const image = nativeImage.createFromPath(dir); if (image.isEmpty()) return "invalid"; - copyFileSync(dir, join(ICONS_DIR, iconName + "_custom.png")); + const img = nativeImage.createFromPath(dir).resize({ width: 128, height: 128 }); + writeFileSync(join(ICONS_DIR, iconName + "_custom.png"), img.toPNG()); return dir; } + +export async function getIconWithBadge(dataURL: string) { + tray.setImage(nativeImage.createFromDataURL(dataURL)); +} diff --git a/src/preload/VesktopNative.ts b/src/preload/VesktopNative.ts index 6ee5671..c2138bc 100644 --- a/src/preload/VesktopNative.ts +++ b/src/preload/VesktopNative.ts @@ -94,6 +94,12 @@ export const VesktopNative = { generateTrayIcons: () => invoke(IpcEvents.GENERATE_TRAY_ICONS), setCurrentVoiceIcon: (listener: (...args: any[]) => void) => { ipcRenderer.on(IpcEvents.SET_CURRENT_VOICE_TRAY_ICON, listener); - } + }, + addBadgeToIcon: (listener: (iconDataURL: string, badgeDataURL: string) => void) => { + ipcRenderer.on(IpcEvents.ADD_BADGE_TO_ICON, (_, iconDataURL: string, badgeDataURL: string) => + listener(iconDataURL, badgeDataURL) + ); + }, + returnIconWithBadge: (dataURL: string) => invoke(IpcEvents.GET_ICON_WITH_BADGE, dataURL) } }; diff --git a/src/renderer/patches/tray.ts b/src/renderer/patches/tray.ts index 3b38964..221264f 100644 --- a/src/renderer/patches/tray.ts +++ b/src/renderer/patches/tray.ts @@ -58,6 +58,36 @@ VesktopNative.tray.createIconRequest(async (iconName: string) => { } }); +VesktopNative.tray.addBadgeToIcon(async (iconDataURL: string, badgeDataURL: string) => { + const canvas = document.createElement("canvas"); + canvas.width = 128; + canvas.height = 128; + + const img = new Image(); + img.width = 128; + img.height = 128; + + img.onload = () => { + const ctx = canvas.getContext("2d"); + if (ctx) { + ctx.drawImage(img, 0, 0); + + const iconImg = new Image(); + iconImg.width = 64; + iconImg.height = 64; + + iconImg.onload = () => { + ctx.drawImage(iconImg, 64, 0, 64, 64); + VesktopNative.tray.returnIconWithBadge(canvas.toDataURL()); + }; + + iconImg.src = badgeDataURL; + } + }; + + img.src = iconDataURL; +}); + VesktopNative.tray.setCurrentVoiceIcon(() => { setCurrentTrayIcon(); }); diff --git a/src/shared/IpcEvents.ts b/src/shared/IpcEvents.ts index 425c959..96eda30 100644 --- a/src/shared/IpcEvents.ts +++ b/src/shared/IpcEvents.ts @@ -59,5 +59,7 @@ export const enum IpcEvents { GENERATE_TRAY_ICONS = "VCD_GENERATE_TRAY_ICONS", SET_CURRENT_VOICE_TRAY_ICON = "VCD_SET_CURRENT_VOICE_ICON", GET_SYSTEM_ACCENT_COLOR = "VCD_GET_ACCENT_COLOR", - SELECT_TRAY_ICON = "VCD_SELECT_TRAY_ICON" + SELECT_TRAY_ICON = "VCD_SELECT_TRAY_ICON", + ADD_BADGE_TO_ICON = "VCD_ADD_BADGE_TO_ICON", + GET_ICON_WITH_BADGE = "VCD_GET_ICON_WITH_BADGE" } diff --git a/static/badges/1.ico b/static/badges/1.ico deleted file mode 100644 index 0e2003f..0000000 Binary files a/static/badges/1.ico and /dev/null differ diff --git a/static/badges/1.png b/static/badges/1.png new file mode 100644 index 0000000..da9c163 Binary files /dev/null and b/static/badges/1.png differ diff --git a/static/badges/10.ico b/static/badges/10.ico deleted file mode 100644 index 1e02de3..0000000 Binary files a/static/badges/10.ico and /dev/null differ diff --git a/static/badges/10.png b/static/badges/10.png new file mode 100644 index 0000000..16bb2da Binary files /dev/null and b/static/badges/10.png differ diff --git a/static/badges/11.ico b/static/badges/11.ico deleted file mode 100644 index 7ebd2ed..0000000 Binary files a/static/badges/11.ico and /dev/null differ diff --git a/static/badges/11.png b/static/badges/11.png new file mode 100644 index 0000000..789bb2d Binary files /dev/null and b/static/badges/11.png differ diff --git a/static/badges/2.ico b/static/badges/2.ico deleted file mode 100644 index d7b6f44..0000000 Binary files a/static/badges/2.ico and /dev/null differ diff --git a/static/badges/2.png b/static/badges/2.png new file mode 100644 index 0000000..5958198 Binary files /dev/null and b/static/badges/2.png differ diff --git a/static/badges/3.ico b/static/badges/3.ico deleted file mode 100644 index 43ce7cf..0000000 Binary files a/static/badges/3.ico and /dev/null differ diff --git a/static/badges/3.png b/static/badges/3.png new file mode 100644 index 0000000..bda3265 Binary files /dev/null and b/static/badges/3.png differ diff --git a/static/badges/4.ico b/static/badges/4.ico deleted file mode 100644 index 91bd61f..0000000 Binary files a/static/badges/4.ico and /dev/null differ diff --git a/static/badges/4.png b/static/badges/4.png new file mode 100644 index 0000000..89c71e0 Binary files /dev/null and b/static/badges/4.png differ diff --git a/static/badges/5.ico b/static/badges/5.ico deleted file mode 100644 index 1d6bf8f..0000000 Binary files a/static/badges/5.ico and /dev/null differ diff --git a/static/badges/5.png b/static/badges/5.png new file mode 100644 index 0000000..b75b3d5 Binary files /dev/null and b/static/badges/5.png differ diff --git a/static/badges/6.ico b/static/badges/6.ico deleted file mode 100644 index d0c0cd6..0000000 Binary files a/static/badges/6.ico and /dev/null differ diff --git a/static/badges/6.png b/static/badges/6.png new file mode 100644 index 0000000..a146e5b Binary files /dev/null and b/static/badges/6.png differ diff --git a/static/badges/7.ico b/static/badges/7.ico deleted file mode 100644 index b50750c..0000000 Binary files a/static/badges/7.ico and /dev/null differ diff --git a/static/badges/7.png b/static/badges/7.png new file mode 100644 index 0000000..8b2cf06 Binary files /dev/null and b/static/badges/7.png differ diff --git a/static/badges/8.ico b/static/badges/8.ico deleted file mode 100644 index 8d0ca36..0000000 Binary files a/static/badges/8.ico and /dev/null differ diff --git a/static/badges/8.png b/static/badges/8.png new file mode 100644 index 0000000..463f2d1 Binary files /dev/null and b/static/badges/8.png differ diff --git a/static/badges/9.ico b/static/badges/9.ico deleted file mode 100644 index c153779..0000000 Binary files a/static/badges/9.ico and /dev/null differ diff --git a/static/badges/9.png b/static/badges/9.png new file mode 100644 index 0000000..2025b41 Binary files /dev/null and b/static/badges/9.png differ