Added custom color support

This commit is contained in:
Oleh Polisan 2024-04-26 16:51:05 +03:00
parent 5e54eb613d
commit b95521ea99
17 changed files with 136 additions and 16 deletions

View file

@ -18,7 +18,7 @@ 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 { getTrayIconFile, mainWin, setTrayIcon } from "./mainWindow";
import { Settings } from "./settings";
import { handle, handleSync } from "./utils/ipcWrappers";
import { PopoutWindows } from "./utils/popout";
@ -154,4 +154,5 @@ watch(
})
);
handle(IpcEvents.SET_TRAY_ICON, (_, iconName: string) => setTrayIcon(iconName));
handle(IpcEvents.SET_TRAY_ICON, (_, iconURI) => setTrayIcon(iconURI));
handle(IpcEvents.GET_TRAY_ICON, (_, iconName) => getTrayIconFile(iconName));

View file

@ -11,10 +11,11 @@ import {
dialog,
Menu,
MenuItemConstructorOptions,
nativeImage,
nativeTheme,
Tray
} from "electron";
import { rm } from "fs/promises";
import { readFile, rm } from "fs/promises";
import { join } from "path";
import { IpcEvents } from "shared/IpcEvents";
import { isTruthy } from "shared/utils/guards";
@ -479,14 +480,21 @@ export async function createWindows() {
initArRPC();
}
export async function setTrayIcon(iconName: string) {
export async function setTrayIcon(iconURI: string) {
if (!tray) return;
if (iconURI !== "" && iconURI !== "icon") {
tray.setImage(nativeImage.createFromDataURL(iconURI));
return;
}
tray.setImage(join(STATIC_DIR, "icon.png"));
}
const Icons = new Set(["speaking", "muted", "deafened", "idle", "icon"]);
export async function getTrayIconFile(iconName: string) {
const Icons = new Set(["speaking", "muted", "deafened", "idle"]);
if (!Icons.has(iconName)) {
console.warn("setTrayIcon: Invalid icon name", iconName);
iconName = "icon";
return readFile(join(STATIC_DIR, "icon.png"));
}
tray.setImage(join(STATIC_DIR, iconName + ".png"));
return readFile(join(STATIC_DIR, iconName + ".svg"), "utf8");
}

View file

@ -25,7 +25,8 @@ export const VesktopNative = {
getVersion: () => sendSync<void>(IpcEvents.GET_VERSION),
setBadgeCount: (count: number) => invoke<void>(IpcEvents.SET_BADGE_COUNT, count),
supportsWindowsTransparency: () => sendSync<boolean>(IpcEvents.SUPPORTS_WINDOWS_TRANSPARENCY),
setTrayIcon: (iconName: string) => invoke<void>(IpcEvents.SET_TRAY_ICON, iconName)
setTrayIcon: (iconURI: string) => invoke<void>(IpcEvents.SET_TRAY_ICON, iconURI),
getTrayIcon: (iconName: string) => invoke<string>(IpcEvents.GET_TRAY_ICON, iconName)
},
autostart: {
isEnabled: () => sendSync<boolean>(IpcEvents.AUTOSTART_ENABLED),

View file

@ -14,6 +14,7 @@ import { isMac, isWindows } from "renderer/utils";
import { AutoStartToggle } from "./AutoStartToggle";
import { DiscordBranchPicker } from "./DiscordBranchPicker";
import { NotificationBadgeToggle } from "./NotificationBadgeToggle";
import { trayIconPicker } from "./TrayColorPicker";
import { VencordLocationPicker } from "./VencordLocationPicker";
import { WindowsTransparencyControls } from "./WindowsTransparencyControls";
@ -75,6 +76,7 @@ const SettingsOptions: Record<string, Array<BooleanSetting | SettingsComponent>>
defaultValue: true,
invisible: () => isMac
},
trayIconPicker,
{
key: "minimizeToTray",
title: "Minimize to tray",

View file

@ -0,0 +1,46 @@
/*
* SPDX-License-Identifier: GPL-3.0
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
* Copyright (c) 2023 Vendicated and Vencord contributors
*/
import "./traySetting.css";
import { Margins } from "@vencord/types/utils";
import { findByCodeLazy } from "@vencord/types/webpack";
import { Forms } from "@vencord/types/webpack/common";
import { setCurrentState } from "renderer/patches/tray";
import { SettingsComponent } from "./Settings";
const ColorPicker = findByCodeLazy(".Messages.USER_SETTINGS_PROFILE_COLOR_SELECT_COLOR", ".BACKGROUND_PRIMARY)");
const presets = [
"#3DB77F", // discord default ~
"#F6BFAC" // Vesktop inpired
];
export const trayIconPicker: SettingsComponent = ({ settings }) => {
if (!settings.tray) return null; // how to disable instead of hiding?
return (
<div className="tray-settings">
<div className="tray-container">
<div className="tray-settings-labels">
<Forms.FormTitle tag="h3">Tray Icon Color</Forms.FormTitle>
<Forms.FormText>Choose a color for your tray icon!</Forms.FormText>
</div>
<ColorPicker
color={parseInt(settings.trayColor ?? "#3DB77F", 16)}
onChange={newColor => {
const hexColor = newColor.toString(16).padStart(6, "0");
settings.trayColor = hexColor;
setCurrentState();
}}
showEyeDropper={false}
suggestedColors={presets}
/>
</div>
<Forms.FormDivider className={Margins.top20 + " " + Margins.bottom20} />
</div>
);
};

View file

@ -0,0 +1,16 @@
.tray-settings {
display: flex;
flex-direction: column;
}
.tray-container {
display: flex;
flex-direction: row;
justify-content: space-between;
}
.tray-settings-labels {
display: flex;
flex-direction: column;
justify-content: flex-start;
}

View file

@ -4,19 +4,49 @@
* Copyright (c) 2023 Vendicated and Vencord contributors
*/
import { Logger } from "@vencord/types/utils";
import { findByPropsLazy, onceReady } from "@vencord/types/webpack";
import { FluxDispatcher, UserStore } from "@vencord/types/webpack/common";
const muteActions = findByPropsLazy("isSelfMute");
const deafActions = findByPropsLazy("isSelfDeaf");
function setCurrentState() {
var inCall = false;
const logger = new Logger("VesktopTrayIcon");
async function changeIconColor(iconName: string) {
const pickedColor = VesktopNative.settings.get().trayColor;
try {
var svg = await VesktopNative.app.getTrayIcon(iconName);
svg = svg.replace(/#f6bfac/gim, "#" + (pickedColor ?? "3DB77F"));
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 dataURL = canvas.toDataURL("image/png");
VesktopNative.app.setTrayIcon(dataURL);
}
};
img.src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`;
} catch (error) {
logger.error("Error: ", error);
}
}
export function setCurrentState() {
if (deafActions.isSelfDeaf()) {
VesktopNative.app.setTrayIcon("deafened");
changeIconColor("deafened");
} else if (muteActions.isSelfMute()) {
VesktopNative.app.setTrayIcon("muted");
changeIconColor("muted");
} else {
VesktopNative.app.setTrayIcon("idle");
changeIconColor("idle");
}
}
@ -26,7 +56,7 @@ onceReady.then(() => {
FluxDispatcher.subscribe("SPEAKING", params => {
if (params.userId === userID) {
if (params.speakingFlags) {
VesktopNative.app.setTrayIcon("speaking");
changeIconColor("speaking");
} else {
setCurrentState();
}
@ -34,18 +64,20 @@ onceReady.then(() => {
});
FluxDispatcher.subscribe("AUDIO_TOGGLE_SELF_DEAF", () => {
setCurrentState();
if (inCall) setCurrentState();
});
FluxDispatcher.subscribe("AUDIO_TOGGLE_SELF_MUTE", () => {
setCurrentState();
if (inCall) setCurrentState();
});
FluxDispatcher.subscribe("RTC_CONNECTION_STATE", params => {
if (params.state === "RTC_CONNECTED") {
inCall = true;
setCurrentState();
} else if (params.state === "RTC_DISCONNECTED") {
VesktopNative.app.setTrayIcon("icon");
inCall = false;
}
});
});

View file

@ -51,5 +51,6 @@ export const enum IpcEvents {
CLIPBOARD_COPY_IMAGE = "VCD_CLIPBOARD_COPY_IMAGE",
SET_TRAY_ICON = "VCD_SET_TRAY_ICON"
SET_TRAY_ICON = "VCD_SET_TRAY_ICON",
GET_TRAY_ICON = "VCD_GET_TRAY_ICON"
}

View file

@ -11,6 +11,7 @@ export interface Settings {
vencordDir?: string;
transparencyOption?: "none" | "mica" | "tabbed" | "acrylic";
tray?: boolean;
trayColor?: string;
minimizeToTray?: boolean;
openLinksWithElectron?: boolean;
staticTitle?: boolean;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

1
static/deafened.svg Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="#FFF" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M12 4c3.87 0 7 3.13 7 7v2h-2.92L21 17.92V11a9 9 0 0 0-9-9c-1.95 0-3.76.62-5.23 1.68l1.44 1.44A6.914 6.914 0 0 1 12 4zM2.27 1.72 1 3l3.33 3.32A8.899 8.899 0 0 0 3 11v7c0 1.66 1.34 3 3 3h3v-8H5v-2c0-1.17.29-2.26.79-3.22L15 17v4h3c.3 0 .59-.06.86-.14L21 23l1.27-1.27-20-20.01z"/><path fill="#f6bfac" d="M2.27 1.72 1 3l20 20 1.27-1.27-20-20.01z"/></svg>

After

Width:  |  Height:  |  Size: 469 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

5
static/idle.svg Normal file
View file

@ -0,0 +1,5 @@
<?xml version="1.0"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1000 1000">
<circle cx="500" cy="500" r="400" stroke="#f6bfac" stroke-width="50" fill="#f6bfac" fill-opacity="0.2"/>
<path d="M 250 500 Q 250 250 500 250" fill="none" stroke="#f6bfac" stroke-width="50"/>
</svg>

After

Width:  |  Height:  |  Size: 294 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

1
static/muted.svg Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="#FFF" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0zm0 0h24v24H0z"/><path d="M19 11h-1.7c0 .74-.16 1.43-.43 2.05l1.23 1.23c.56-.98.9-2.09.9-3.28zm-4.02.17c0-.06.02-.11.02-.17V5c0-1.66-1.34-3-3-3S9 3.34 9 5v.18l5.98 5.99zM9.02 10.28V11c0 1.66 1.33 3 2.99 3 .22 0 .44-.03.65-.08l1.66 1.66c-.71.33-1.5.52-2.31.52-2.76 0-5.3-2.1-5.3-5.1H5c0 3.41 2.72 6.23 6 6.72V21h2v-3.28c.91-.13 1.77-.45 2.54-.9L19.73 21 21 19.73 4.27 3z"/><path fill="#f6bfac" d="M4.27 3 3 4.27l6.02 6.01L19.73 21 21 19.73 4.27 3"/></svg>

After

Width:  |  Height:  |  Size: 559 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

5
static/speaking.svg Normal file
View file

@ -0,0 +1,5 @@
<?xml version="1.0"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1000 1000">
<circle cx="500" cy="500" r="400" stroke="#f6bfac" stroke-width="50" fill="#f6bfac"/>
<path d="M 250 500 Q 250 250 500 250" fill="none" stroke="white" stroke-width="50"/>
</svg>

After

Width:  |  Height:  |  Size: 273 B