feat: overrides by each icon type

This commit is contained in:
Oleh Polisan 2024-06-20 23:34:05 +03:00
parent 12e9e61b1e
commit f1c210edb3
8 changed files with 138 additions and 100 deletions

View file

@ -165,6 +165,8 @@ handle(IpcEvents.SET_TRAY_ICON, (_, iconURI) => setTrayIcon(iconURI));
handle(IpcEvents.GET_TRAY_ICON, (_, iconPath) => getTrayIconFile(iconPath)); handle(IpcEvents.GET_TRAY_ICON, (_, iconPath) => getTrayIconFile(iconPath));
handleSync(IpcEvents.GET_TRAY_ICON_SYNC, (_, iconPath) => getTrayIconFileSync(iconPath)); handleSync(IpcEvents.GET_TRAY_ICON_SYNC, (_, iconPath) => getTrayIconFileSync(iconPath));
handle(IpcEvents.GET_SYSTEM_ACCENT_COLOR, () => `#${systemPreferences.getAccentColor?.() || ""}`); 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.GENERATE_TRAY_ICONS, () => generateTrayIcons());
handle(IpcEvents.SELECT_TRAY_ICON, async (_, iconName) => pickTrayIcon(iconName)); handle(IpcEvents.SELECT_TRAY_ICON, async (_, iconName) => pickTrayIcon(iconName));

View file

@ -11,6 +11,7 @@ import {
dialog, dialog,
Menu, Menu,
MenuItemConstructorOptions, MenuItemConstructorOptions,
NativeImage,
nativeImage, nativeImage,
nativeTheme, nativeTheme,
screen, screen,
@ -39,7 +40,7 @@ import {
} from "./constants"; } from "./constants";
import { Settings, State, VencordSettings } from "./settings"; import { Settings, State, VencordSettings } from "./settings";
import { createSplashWindow } from "./splash"; import { createSplashWindow } from "./splash";
import { generateTrayIcons } from "./tray"; import { generateTrayIcons, isCustomIcon, statusToSettingsKey } from "./tray";
import { makeLinksOpenExternally } from "./utils/makeLinksOpenExternally"; import { makeLinksOpenExternally } from "./utils/makeLinksOpenExternally";
import { applyDeckKeyboardFix, askToApplySteamLayout, isDeckGameMode } from "./utils/steamOS"; import { applyDeckKeyboardFix, askToApplySteamLayout, isDeckGameMode } from "./utils/steamOS";
import { downloadVencordFiles, ensureVencordFiles } from "./utils/vencordLoader"; import { downloadVencordFiles, ensureVencordFiles } from "./utils/vencordLoader";
@ -127,10 +128,12 @@ function initTray(win: BrowserWindow) {
tray = new Tray(ICON_PATH); tray = new Tray(ICON_PATH);
try { 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) { } catch (error) {
console.log("Error while loading custom tray image. Recreating new ones."); console.log("Error while loading custom tray image. Recreating new ones.");
generateTrayIcons(true); Settings.store.trayMainOverride = false;
generateTrayIcons();
} }
tray.setToolTip("Vesktop"); tray.setToolTip("Vesktop");
tray.setContextMenu(trayMenu); tray.setContextMenu(trayMenu);
@ -513,17 +516,30 @@ export async function createWindows() {
export async function setTrayIcon(iconName: string) { export async function setTrayIcon(iconName: string) {
if (!tray || tray.isDestroyed()) return; if (!tray || tray.isDestroyed()) return;
const Icons = new Set(["speaking", "muted", "deafened", "idle", "icon"]); const Icons = new Set(["speaking", "muted", "deafened", "idle", "icon"]);
if (Icons.has(iconName)) {
if (!Icons.has(iconName)) return;
try { try {
var trayImage = nativeImage.createFromPath(join(ICONS_DIR, iconName + ".png")); 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") { if (process.platform === "darwin") {
trayImage = trayImage.resize({ width: 16, height: 16 }); trayImage = trayImage.resize({ width: 16, height: 16 });
} }
tray.setImage(trayImage); tray.setImage(trayImage);
} catch (error) { } catch (error) {
console.log("Error: ", error, "Regenerating tray icons."); console.log("Error: ", error, "Regenerating tray icons.");
generateTrayIcons(true); generateTrayIcons(); // TODO: pass here only one icon request
} }
return; return;
} }
}

View file

@ -4,7 +4,7 @@
* Copyright (c) 2023 Vendicated and Vencord contributors * 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 { copyFileSync, mkdirSync, writeFileSync } from "fs";
import { readFile } from "fs/promises"; import { readFile } from "fs/promises";
import { join } from "path"; import { join } from "path";
@ -14,6 +14,19 @@ import { ICONS_DIR, STATIC_DIR } from "shared/paths";
import { mainWin } from "./mainWindow"; import { mainWin } from "./mainWindow";
import { Settings } from "./settings"; 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) { export async function getTrayIconFile(iconName: string) {
const Icons = new Set(["speaking", "muted", "deafened", "idle"]); const Icons = new Set(["speaking", "muted", "deafened", "idle"]);
if (!Icons.has(iconName)) { if (!Icons.has(iconName)) {
@ -26,25 +39,39 @@ export async function getTrayIconFile(iconName: string) {
export function getTrayIconFileSync(iconName: string) { export function getTrayIconFileSync(iconName: string) {
// returns dataURL of image from TrayIcons folder // returns dataURL of image from TrayIcons folder
const Icons = new Set(["speaking", "muted", "deafened", "idle", "icon"]); const Icons = new Set(["speaking", "muted", "deafened", "idle", "icon"]);
if (Icons.has(iconName)) { if (Icons.has(iconName)) {
const img = nativeImage.createFromPath(join(ICONS_DIR, iconName + ".png")).resize({ width: 128, height: 128 }); var img: NativeImage;
if (img.isEmpty()) return nativeImage.createFromPath(join(ICONS_DIR, iconName + ".png")).toDataURL(); 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(); 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 // creates .png at config/TrayIcons/iconName.png from given iconDataURL
// primarily called from renderer using CREATE_TRAY_ICON_RESPONSE IPC call // primarily called from renderer using CREATE_TRAY_ICON_RESPONSE IPC call
iconDataURL = iconDataURL.replace(/^data:image\/png;base64,/, ""); iconDataURL = iconDataURL.replace(/^data:image\/png;base64,/, "");
if (isCustomIcon) {
writeFileSync(join(ICONS_DIR, iconName + "_custom.png"), iconDataURL, "base64");
} else {
writeFileSync(join(ICONS_DIR, iconName + ".png"), iconDataURL, "base64"); writeFileSync(join(ICONS_DIR, iconName + ".png"), iconDataURL, "base64");
}
mainWin.webContents.send(IpcEvents.SET_CURRENT_VOICE_TRAY_ICON); 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 // this function generates tray icons as .png's in Vesktop cache for future use
mkdirSync(ICONS_DIR, { recursive: true }); mkdirSync(ICONS_DIR, { recursive: true });
if (force || !Settings.store.trayCustom) {
const Icons = ["speaking", "muted", "deafened", "idle"]; const Icons = ["speaking", "muted", "deafened", "idle"];
for (const icon of Icons) { for (const icon of Icons) {
mainWin.webContents.send(IpcEvents.CREATE_TRAY_ICON_REQUEST, icon); mainWin.webContents.send(IpcEvents.CREATE_TRAY_ICON_REQUEST, icon);
@ -52,7 +79,6 @@ export async function generateTrayIcons(force = false) {
copyFileSync(join(STATIC_DIR, "icon.png"), join(ICONS_DIR, "icon.png")); copyFileSync(join(STATIC_DIR, "icon.png"), join(ICONS_DIR, "icon.png"));
mainWin.webContents.send(IpcEvents.SET_CURRENT_VOICE_TRAY_ICON); mainWin.webContents.send(IpcEvents.SET_CURRENT_VOICE_TRAY_ICON);
} }
}
export async function pickTrayIcon(iconName: string) { export async function pickTrayIcon(iconName: string) {
const Icons = new Set(["speaking", "muted", "deafened", "idle", "icon"]); const Icons = new Set(["speaking", "muted", "deafened", "idle", "icon"]);
@ -67,6 +93,6 @@ export async function pickTrayIcon(iconName: string) {
// add .svg !! // add .svg !!
const image = nativeImage.createFromPath(dir); const image = nativeImage.createFromPath(dir);
if (image.isEmpty()) return "invalid"; if (image.isEmpty()) return "invalid";
copyFileSync(dir, join(ICONS_DIR, iconName + ".png")); copyFileSync(dir, join(ICONS_DIR, iconName + "_custom.png"));
return dir; return dir;
} }

View file

@ -86,8 +86,8 @@ export const VesktopNative = {
setIcon: (iconURI: string) => invoke<void>(IpcEvents.SET_TRAY_ICON, iconURI), setIcon: (iconURI: string) => invoke<void>(IpcEvents.SET_TRAY_ICON, iconURI),
getIcon: (iconName: string) => invoke<string>(IpcEvents.GET_TRAY_ICON, iconName), getIcon: (iconName: string) => invoke<string>(IpcEvents.GET_TRAY_ICON, iconName),
getIconSync: (iconName: string) => sendSync<string>(IpcEvents.GET_TRAY_ICON_SYNC, iconName), getIconSync: (iconName: string) => sendSync<string>(IpcEvents.GET_TRAY_ICON_SYNC, iconName),
createIconResponse: (iconName: string, iconDataURL: string) => createIconResponse: (iconName: string, iconDataURL: string, isCustomIcon: boolean = true) =>
invoke<void>(IpcEvents.CREATE_TRAY_ICON_RESPONSE, iconName, iconDataURL), invoke<void>(IpcEvents.CREATE_TRAY_ICON_RESPONSE, iconName, iconDataURL, isCustomIcon),
createIconRequest: (listener: (iconName: string) => void) => { createIconRequest: (listener: (iconName: string) => void) => {
ipcRenderer.on(IpcEvents.CREATE_TRAY_ICON_REQUEST, (_, iconPath: string) => listener(iconPath)); ipcRenderer.on(IpcEvents.CREATE_TRAY_ICON_REQUEST, (_, iconPath: string) => listener(iconPath));
}, },

View file

@ -8,7 +8,7 @@ import "./traySetting.css";
import { Margins, Modals, ModalSize, openModal } from "@vencord/types/utils"; import { Margins, Modals, ModalSize, openModal } from "@vencord/types/utils";
import { findByCodeLazy, findByPropsLazy } from "@vencord/types/webpack"; 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 { setCurrentTrayIcon } from "renderer/patches/tray";
import { useSettings } from "renderer/settings"; import { useSettings } from "renderer/settings";
import { isLinux } from "renderer/utils"; import { isLinux } from "renderer/utils";
@ -35,7 +35,16 @@ if (!isLinux)
if (color) presets.unshift(color); 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) { function trayEditButton(iconName: string) {
const Settings = useSettings();
return ( return (
<div className="vcd-tray-icon-wrap"> <div className="vcd-tray-icon-wrap">
<img <img
@ -63,15 +72,15 @@ function trayEditButton(iconName: string) {
}); });
return; return;
} }
console.log("choice:", choice);
// copy image and reload const iconKey = statusToSettingsKey[iconName as keyof typeof statusToSettingsKey].key;
// settings.trayIconPath = choice; Settings[iconKey] = true;
const iconDataURL = VesktopNative.tray.getIconSync(iconName); const iconDataURL = VesktopNative.tray.getIconSync(iconName);
const img = document.getElementById(iconName) as HTMLImageElement; const img = document.getElementById(iconName) as HTMLImageElement;
if (img) { if (img) {
img.src = iconDataURL; img.src = iconDataURL;
} }
VesktopNative.tray.createIconResponse(iconName, iconDataURL); setCurrentTrayIcon();
}} }}
/> />
</div> </div>
@ -80,6 +89,7 @@ function trayEditButton(iconName: string) {
function TrayModalComponent({ modalProps, close }: { modalProps: any; close: () => void }) { function TrayModalComponent({ modalProps, close }: { modalProps: any; close: () => void }) {
const Settings = useSettings(); const Settings = useSettings();
return ( return (
<Modals.ModalRoot {...modalProps} size={ModalSize.MEDIUM}> <Modals.ModalRoot {...modalProps} size={ModalSize.MEDIUM}>
<Modals.ModalHeader className="vcd-custom-tray-header"> <Modals.ModalHeader className="vcd-custom-tray-header">
@ -87,45 +97,27 @@ function TrayModalComponent({ modalProps, close }: { modalProps: any; close: ()
<Modals.ModalCloseButton onClick={close} /> <Modals.ModalCloseButton onClick={close} />
</Modals.ModalHeader> </Modals.ModalHeader>
<Modals.ModalContent className="vcd-custom-tray-modal"> <Modals.ModalContent className="vcd-custom-tray-modal">
<Forms.FormDivider className={Margins.top8 + " " + Margins.bottom8} /> {Object.entries(statusToSettingsKey).map(([status, { key, label }]) => (
<div key={status}>
<Forms.FormSection className="vcd-custom-tray-icon-section"> <Forms.FormSection className="vcd-custom-tray-icon-section">
<Forms.FormText className={Margins.top16 + " vcd-custom-tray-icon-form-text"}> <div className="vcd-custom-tray-icon-label">
Main icon {trayEditButton(status)}
</Forms.FormText> <Forms.FormText>{label}</Forms.FormText>
{trayEditButton("icon")} </div>
</Forms.FormSection> {Settings[key] && (
<Button
<Forms.FormDivider className={Margins.top8 + " " + Margins.bottom8} /> onClick={() => {
<Forms.FormSection className="vcd-custom-tray-icon-section"> Settings[key] = false;
<Forms.FormText className={Margins.top16 + " vcd-custom-tray-icon-form-text"}> setCurrentTrayIcon();
Idle icon }}
</Forms.FormText> >
{trayEditButton("idle")} Reset
</Forms.FormSection> </Button>
)}
<Forms.FormDivider className={Margins.top8 + " " + Margins.bottom8} />
<Forms.FormSection className="vcd-custom-tray-icon-section">
<Forms.FormText className={Margins.top16 + " vcd-custom-tray-icon-form-text"}>
Speaking icon
</Forms.FormText>
{trayEditButton("speaking")}
</Forms.FormSection>
<Forms.FormDivider className={Margins.top8 + " " + Margins.bottom8} />
<Forms.FormSection className="vcd-custom-tray-icon-section">
<Forms.FormText className={Margins.top16 + " vcd-custom-tray-icon-form-text"}>
Muted icon
</Forms.FormText>
{trayEditButton("muted")}
</Forms.FormSection>
<Forms.FormDivider className={Margins.top8 + " " + Margins.bottom8} />
<Forms.FormSection className="vcd-custom-tray-icon-section">
<Forms.FormText className={Margins.top16 + " vcd-custom-tray-icon-form-text"}>
Deafened icon
</Forms.FormText>
{trayEditButton("deafened")}
</Forms.FormSection> </Forms.FormSection>
<Forms.FormDivider className={`${Margins.top8} ${Margins.bottom8}`} />
</div>
))}
</Modals.ModalContent> </Modals.ModalContent>
<Modals.ModalFooter></Modals.ModalFooter> <Modals.ModalFooter></Modals.ModalFooter>
</Modals.ModalRoot> </Modals.ModalRoot>
@ -156,37 +148,28 @@ export const CustomizeTraySwitch: SettingsComponent = ({ settings }) => {
return ( return (
<> <>
<div id="vcd-tray-setting"> <div className="vcd-tray-settings">
<div className="vcd-tray-setting-switch"> <div className="vcd-tray-container">
<Switch <div className="vcd-tray-settings-labels">
key="tray" <Forms.FormTitle tag="h3">Custom tray icons</Forms.FormTitle>
value={settings.trayCustom ?? false} <Forms.FormText>Use custom default and voice status tray icons.</Forms.FormText>
onChange={v => (settings.trayCustom = v)}
note={"Use custom default and voice status tray icons."}
>
Use custom tray icons
</Switch>
</div> </div>
<div className="vcd-tray-setting-customize"> <Button
<Forms.FormText> onClick={async () => {
<a
href="about:blank"
onClick={e => {
e.preventDefault();
openTrayModal(); openTrayModal();
}} }}
> >
Configure Configure
</a> </Button>
</Forms.FormText>
</div> </div>
<Forms.FormDivider className={Margins.top20 + " " + Margins.bottom20} />
</div> </div>
</> </>
); );
}; };
export const TrayIconPicker: SettingsComponent = ({ settings }) => { export const TrayIconPicker: SettingsComponent = ({ settings }) => {
if (!settings.tray || settings.trayCustom) return null; if (!settings.tray) return null;
return ( return (
<div className="vcd-tray-settings"> <div className="vcd-tray-settings">
<div className="vcd-tray-container"> <div className="vcd-tray-container">
@ -211,7 +194,7 @@ export const TrayIconPicker: SettingsComponent = ({ settings }) => {
}; };
export const TrayFillColorSwitch: SettingsComponent = ({ settings }) => { export const TrayFillColorSwitch: SettingsComponent = ({ settings }) => {
if (!settings.tray || settings.trayCustom) return null; if (!settings.tray) return null;
return ( return (
<div className="vcd-tray-settings"> <div className="vcd-tray-settings">
<div className="vcd-tray-container"> <div className="vcd-tray-container">

View file

@ -76,8 +76,15 @@
display: flex; display: flex;
flex-direction: row; flex-direction: row;
justify-content: space-between; justify-content: space-between;
align-items: center;
} }
.vcd-custom-tray-icon-form-text { .vcd-custom-tray-icon-form-text {
font-size: medium; font-size: medium;
} }
.vcd-custom-tray-icon-label {
margin-top: 0.5em;
display: flex;
align-items: center;
}

View file

@ -49,7 +49,7 @@ VesktopNative.tray.createIconRequest(async (iconName: string) => {
if (ctx) { if (ctx) {
ctx.drawImage(img, 0, 0); ctx.drawImage(img, 0, 0);
const dataURL = canvas.toDataURL("image/png"); 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)}`; img.src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`;

View file

@ -13,7 +13,11 @@ export interface Settings {
tray?: boolean; tray?: boolean;
trayColor?: string; trayColor?: string;
trayAutoFill?: "auto" | "white" | "black"; trayAutoFill?: "auto" | "white" | "black";
trayCustom?: boolean; trayMainOverride?: boolean;
trayIdleOverride?: boolean;
trayMutedOverride?: boolean;
traySpeakingOverride?: boolean;
trayDeafenedOverride?: boolean;
minimizeToTray?: boolean; minimizeToTray?: boolean;
openLinksWithElectron?: boolean; openLinksWithElectron?: boolean;
staticTitle?: boolean; staticTitle?: boolean;