diff --git a/src/main/ipc.ts b/src/main/ipc.ts index c48a6ab..bfe9208 100644 --- a/src/main/ipc.ts +++ b/src/main/ipc.ts @@ -165,6 +165,8 @@ handle(IpcEvents.SET_TRAY_ICON, (_, iconURI) => setTrayIcon(iconURI)); handle(IpcEvents.GET_TRAY_ICON, (_, iconPath) => getTrayIconFile(iconPath)); handleSync(IpcEvents.GET_TRAY_ICON_SYNC, (_, iconPath) => getTrayIconFileSync(iconPath)); handle(IpcEvents.GET_SYSTEM_ACCENT_COLOR, () => `#${systemPreferences.getAccentColor?.() || ""}`); -handle(IpcEvents.CREATE_TRAY_ICON_RESPONSE, (_, iconName, dataURL) => createTrayIcon(iconName, dataURL)); +handle(IpcEvents.CREATE_TRAY_ICON_RESPONSE, (_, iconName, dataURL, isCustomIcon) => + createTrayIcon(iconName, dataURL, isCustomIcon) +); handle(IpcEvents.GENERATE_TRAY_ICONS, () => generateTrayIcons()); handle(IpcEvents.SELECT_TRAY_ICON, async (_, iconName) => pickTrayIcon(iconName)); diff --git a/src/main/mainWindow.ts b/src/main/mainWindow.ts index 29f3fb2..cd60521 100755 --- a/src/main/mainWindow.ts +++ b/src/main/mainWindow.ts @@ -11,6 +11,7 @@ import { dialog, Menu, MenuItemConstructorOptions, + NativeImage, nativeImage, nativeTheme, screen, @@ -39,7 +40,7 @@ import { } from "./constants"; import { Settings, State, VencordSettings } from "./settings"; import { createSplashWindow } from "./splash"; -import { generateTrayIcons } from "./tray"; +import { generateTrayIcons, isCustomIcon, statusToSettingsKey } from "./tray"; import { makeLinksOpenExternally } from "./utils/makeLinksOpenExternally"; import { applyDeckKeyboardFix, askToApplySteamLayout, isDeckGameMode } from "./utils/steamOS"; import { downloadVencordFiles, ensureVencordFiles } from "./utils/vencordLoader"; @@ -127,10 +128,12 @@ function initTray(win: BrowserWindow) { tray = new Tray(ICON_PATH); try { - if (Settings.store.trayCustom) tray.setImage(join(ICONS_DIR, "icon.png")); + 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."); - generateTrayIcons(true); + Settings.store.trayMainOverride = false; + generateTrayIcons(); } tray.setToolTip("Vesktop"); tray.setContextMenu(trayMenu); @@ -513,17 +516,30 @@ export async function createWindows() { export async function setTrayIcon(iconName: string) { if (!tray || tray.isDestroyed()) return; const Icons = new Set(["speaking", "muted", "deafened", "idle", "icon"]); - if (Icons.has(iconName)) { - try { - var trayImage = nativeImage.createFromPath(join(ICONS_DIR, iconName + ".png")); - if (process.platform === "darwin") { - trayImage = trayImage.resize({ width: 16, height: 16 }); + + 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; } - tray.setImage(trayImage); - } catch (error) { - console.log("Error: ", error, "Regenerating tray icons."); - generateTrayIcons(true); + } else trayImage = nativeImage.createFromPath(join(ICONS_DIR, iconName + ".png")); + if (trayImage.isEmpty()) { + generateTrayIcons(); + return; } - 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 f3d3f0c..0525ec3 100644 --- a/src/main/tray.ts +++ b/src/main/tray.ts @@ -4,7 +4,7 @@ * Copyright (c) 2023 Vendicated and Vencord contributors */ -import { dialog, nativeImage } from "electron"; +import { dialog, NativeImage, nativeImage } from "electron"; import { copyFileSync, mkdirSync, writeFileSync } from "fs"; import { readFile } from "fs/promises"; import { join } from "path"; @@ -14,6 +14,19 @@ import { ICONS_DIR, STATIC_DIR } from "shared/paths"; import { mainWin } from "./mainWindow"; import { Settings } from "./settings"; +export const statusToSettingsKey = { + speaking: "traySpeakingOverride", + muted: "trayMutedOverride", + deafened: "trayDeafenedOverride", + idle: "trayIdleOverride", + icon: "trayMainOverride" +}; + +export const isCustomIcon = (status: string) => { + const settingKey = statusToSettingsKey[status as keyof typeof statusToSettingsKey]; + return Settings.store[settingKey]; +}; + export async function getTrayIconFile(iconName: string) { const Icons = new Set(["speaking", "muted", "deafened", "idle"]); if (!Icons.has(iconName)) { @@ -26,32 +39,45 @@ export async function getTrayIconFile(iconName: string) { export function getTrayIconFileSync(iconName: string) { // returns dataURL of image from TrayIcons folder const Icons = new Set(["speaking", "muted", "deafened", "idle", "icon"]); + if (Icons.has(iconName)) { - const img = nativeImage.createFromPath(join(ICONS_DIR, iconName + ".png")).resize({ width: 128, height: 128 }); - if (img.isEmpty()) return nativeImage.createFromPath(join(ICONS_DIR, iconName + ".png")).toDataURL(); + var img: NativeImage; + if (isCustomIcon(iconName)) { + img = nativeImage.createFromPath(join(ICONS_DIR, iconName + "_custom.png")); + } else img = nativeImage.createFromPath(join(ICONS_DIR, iconName + ".png")); + img = img.resize({ width: 128, height: 128 }); + if (img.isEmpty()) { + console.log("Can't open icon file. Regenerating."); + generateTrayIcons(); + img = nativeImage.createFromPath(join(ICONS_DIR, iconName + ".png")); + const iconKey = statusToSettingsKey[iconName as keyof typeof statusToSettingsKey]; + Settings.store[iconKey] = false; + } return img.toDataURL(); } } -export async function createTrayIcon(iconName: string, iconDataURL: string) { +export async function createTrayIcon(iconName: string, iconDataURL: string, isCustomIcon: boolean = false) { // creates .png at config/TrayIcons/iconName.png from given iconDataURL // primarily called from renderer using CREATE_TRAY_ICON_RESPONSE IPC call iconDataURL = iconDataURL.replace(/^data:image\/png;base64,/, ""); - writeFileSync(join(ICONS_DIR, iconName + ".png"), iconDataURL, "base64"); + if (isCustomIcon) { + writeFileSync(join(ICONS_DIR, iconName + "_custom.png"), iconDataURL, "base64"); + } else { + writeFileSync(join(ICONS_DIR, iconName + ".png"), iconDataURL, "base64"); + } mainWin.webContents.send(IpcEvents.SET_CURRENT_VOICE_TRAY_ICON); } -export async function generateTrayIcons(force = false) { +export async function generateTrayIcons() { // this function generates tray icons as .png's in Vesktop cache for future use mkdirSync(ICONS_DIR, { recursive: true }); - if (force || !Settings.store.trayCustom) { - const Icons = ["speaking", "muted", "deafened", "idle"]; - 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")); - mainWin.webContents.send(IpcEvents.SET_CURRENT_VOICE_TRAY_ICON); + const Icons = ["speaking", "muted", "deafened", "idle"]; + 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")); + mainWin.webContents.send(IpcEvents.SET_CURRENT_VOICE_TRAY_ICON); } export async function pickTrayIcon(iconName: string) { @@ -67,6 +93,6 @@ export async function pickTrayIcon(iconName: string) { // add .svg !! const image = nativeImage.createFromPath(dir); if (image.isEmpty()) return "invalid"; - copyFileSync(dir, join(ICONS_DIR, iconName + ".png")); + copyFileSync(dir, join(ICONS_DIR, iconName + "_custom.png")); return dir; } diff --git a/src/preload/VesktopNative.ts b/src/preload/VesktopNative.ts index a60005f..6ee5671 100644 --- a/src/preload/VesktopNative.ts +++ b/src/preload/VesktopNative.ts @@ -86,8 +86,8 @@ export const VesktopNative = { setIcon: (iconURI: string) => invoke(IpcEvents.SET_TRAY_ICON, iconURI), getIcon: (iconName: string) => invoke(IpcEvents.GET_TRAY_ICON, iconName), getIconSync: (iconName: string) => sendSync(IpcEvents.GET_TRAY_ICON_SYNC, iconName), - createIconResponse: (iconName: string, iconDataURL: string) => - invoke(IpcEvents.CREATE_TRAY_ICON_RESPONSE, iconName, iconDataURL), + createIconResponse: (iconName: string, iconDataURL: string, isCustomIcon: boolean = true) => + invoke(IpcEvents.CREATE_TRAY_ICON_RESPONSE, iconName, iconDataURL, isCustomIcon), createIconRequest: (listener: (iconName: string) => void) => { ipcRenderer.on(IpcEvents.CREATE_TRAY_ICON_REQUEST, (_, iconPath: string) => listener(iconPath)); }, diff --git a/src/renderer/components/settings/TraySettings.tsx b/src/renderer/components/settings/TraySettings.tsx index 9a208b5..b9d3a4e 100644 --- a/src/renderer/components/settings/TraySettings.tsx +++ b/src/renderer/components/settings/TraySettings.tsx @@ -8,7 +8,7 @@ import "./traySetting.css"; import { Margins, Modals, ModalSize, openModal } from "@vencord/types/utils"; import { findByCodeLazy, findByPropsLazy } from "@vencord/types/webpack"; -import { Forms, Select, Switch, Toasts } from "@vencord/types/webpack/common"; +import { Button, Forms, Select, Switch, Toasts } from "@vencord/types/webpack/common"; import { setCurrentTrayIcon } from "renderer/patches/tray"; import { useSettings } from "renderer/settings"; import { isLinux } from "renderer/utils"; @@ -35,7 +35,16 @@ if (!isLinux) if (color) presets.unshift(color); }); +const statusToSettingsKey = { + icon: { key: "trayMainOverride", label: "Main Icon" }, + idle: { key: "trayIdleOverride", label: "Idle icon" }, + speaking: { key: "traySpeakingOverride", label: "Speaking icon" }, + muted: { key: "trayMutedOverride", label: "Muted icon" }, + deafened: { key: "trayDeafenedOverride", label: "Deafened icon" } +}; + function trayEditButton(iconName: string) { + const Settings = useSettings(); return (
@@ -80,6 +89,7 @@ function trayEditButton(iconName: string) { function TrayModalComponent({ modalProps, close }: { modalProps: any; close: () => void }) { const Settings = useSettings(); + return ( @@ -87,45 +97,27 @@ function TrayModalComponent({ modalProps, close }: { modalProps: any; close: () - - - - Main icon - - {trayEditButton("icon")} - - - - - - Idle icon - - {trayEditButton("idle")} - - - - - - Speaking icon - - {trayEditButton("speaking")} - - - - - - Muted icon - - {trayEditButton("muted")} - - - - - - Deafened icon - - {trayEditButton("deafened")} - + {Object.entries(statusToSettingsKey).map(([status, { key, label }]) => ( +
+ +
+ {trayEditButton(status)} + {label} +
+ {Settings[key] && ( + + )} +
+ +
+ ))}
@@ -156,37 +148,28 @@ export const CustomizeTraySwitch: SettingsComponent = ({ settings }) => { return ( <> -
-
- (settings.trayCustom = v)} - note={"Use custom default and voice status tray icons."} +
+
+
+ Custom tray icons + Use custom default and voice status tray icons. +
+
- +
); }; export const TrayIconPicker: SettingsComponent = ({ settings }) => { - if (!settings.tray || settings.trayCustom) return null; + if (!settings.tray) return null; return (
@@ -211,7 +194,7 @@ export const TrayIconPicker: SettingsComponent = ({ settings }) => { }; export const TrayFillColorSwitch: SettingsComponent = ({ settings }) => { - if (!settings.tray || settings.trayCustom) return null; + if (!settings.tray) return null; return (
diff --git a/src/renderer/components/settings/traySetting.css b/src/renderer/components/settings/traySetting.css index db43b22..319ce33 100644 --- a/src/renderer/components/settings/traySetting.css +++ b/src/renderer/components/settings/traySetting.css @@ -76,8 +76,15 @@ display: flex; flex-direction: row; justify-content: space-between; + align-items: center; } .vcd-custom-tray-icon-form-text { font-size: medium; +} + +.vcd-custom-tray-icon-label { + margin-top: 0.5em; + display: flex; + align-items: center; } \ No newline at end of file diff --git a/src/renderer/patches/tray.ts b/src/renderer/patches/tray.ts index be3f46f..3b38964 100644 --- a/src/renderer/patches/tray.ts +++ b/src/renderer/patches/tray.ts @@ -49,7 +49,7 @@ VesktopNative.tray.createIconRequest(async (iconName: string) => { if (ctx) { ctx.drawImage(img, 0, 0); const dataURL = canvas.toDataURL("image/png"); - VesktopNative.tray.createIconResponse(iconName, dataURL); + VesktopNative.tray.createIconResponse(iconName, dataURL, false); } }; img.src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`; diff --git a/src/shared/settings.d.ts b/src/shared/settings.d.ts index 6e43722..68ae6b9 100644 --- a/src/shared/settings.d.ts +++ b/src/shared/settings.d.ts @@ -13,7 +13,11 @@ export interface Settings { tray?: boolean; trayColor?: string; trayAutoFill?: "auto" | "white" | "black"; - trayCustom?: boolean; + trayMainOverride?: boolean; + trayIdleOverride?: boolean; + trayMutedOverride?: boolean; + traySpeakingOverride?: boolean; + trayDeafenedOverride?: boolean; minimizeToTray?: boolean; openLinksWithElectron?: boolean; staticTitle?: boolean;