Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Tray icon voice detection, customization and notification badge #517

Open
wants to merge 86 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 66 commits
Commits
Show all changes
86 commits
Select commit Hold shift + click to select a range
9c6b5f9
Added new tray icons
PolisanTheEasyNick Apr 18, 2024
05def05
Added tray icon voice detection
PolisanTheEasyNick Apr 18, 2024
8d77870
removed unused imports
PolisanTheEasyNick Apr 18, 2024
2dc3ff6
Fixed for tests
PolisanTheEasyNick Apr 18, 2024
eec5eb1
Removed unused logger
PolisanTheEasyNick Apr 18, 2024
68a55e2
Fixed icon when connected with muted status
PolisanTheEasyNick Apr 18, 2024
c3d5dbd
added ability to change icon to unread
PolisanTheEasyNick Apr 18, 2024
6108823
Revert "added ability to change icon to unread"
PolisanTheEasyNick Apr 18, 2024
5b3c9b5
Increased border width
PolisanTheEasyNick Apr 19, 2024
8c6d68e
Updated and resized icons
PolisanTheEasyNick Apr 20, 2024
b9e5ad3
changes according to reviews
PolisanTheEasyNick Apr 20, 2024
b89a10e
fixed bug with not changing to default icon
PolisanTheEasyNick Apr 20, 2024
5e54eb6
Merge branch 'main' into tray-icon
PolisanTheEasyNick Apr 21, 2024
b95521e
Added custom color support
PolisanTheEasyNick Apr 26, 2024
b268a59
Merge branch 'main' into tray-icon
PolisanTheEasyNick Apr 26, 2024
b8a12a9
resolved due to reviews
PolisanTheEasyNick Apr 26, 2024
562095e
fixed case in Settings.tsx
PolisanTheEasyNick Apr 26, 2024
b314f62
svg rework, add proposal for accent color (windows,linux), some bug f…
PolisanTheEasyNick Apr 27, 2024
4aee841
add more colors to presets
PolisanTheEasyNick Apr 27, 2024
3098b58
fixed check for tray availability
PolisanTheEasyNick Apr 27, 2024
da9f14d
Merge branch 'main' into tray-icon
PolisanTheEasyNick May 1, 2024
965522d
fixed tray updating in minimized window at Windows. This also fixes r…
PolisanTheEasyNick May 2, 2024
2f3cafc
Merge branch 'main' into tray-icon
PolisanTheEasyNick May 2, 2024
049891c
Merge branch 'main' into tray-icon
PolisanTheEasyNick May 3, 2024
f44f1b2
Merge branch 'main' into tray-icon
PolisanTheEasyNick May 5, 2024
c342a9a
Merge branch 'main' into tray-icon
PolisanTheEasyNick May 6, 2024
92888eb
Merge branch 'main' into tray-icon
PolisanTheEasyNick May 8, 2024
f2bfba0
Merge branch 'main' into tray-icon
PolisanTheEasyNick May 14, 2024
36ebc20
Merge branch 'Vencord:main' into tray-icon
PolisanTheEasyNick May 20, 2024
366c08a
Removed backgroundThrottling setting
PolisanTheEasyNick May 20, 2024
e191276
Merge branch 'main' into tray-icon
PolisanTheEasyNick May 23, 2024
ce1a322
Merge branch 'main' into tray-icon
PolisanTheEasyNick May 24, 2024
261aea8
use one voiceActions for both mute and deaf
PolisanTheEasyNick May 24, 2024
c931d7d
Merge branch 'Vencord:main' into tray-icon
PolisanTheEasyNick Jun 17, 2024
d44df8a
fix: show proper icon after toggle setting in VC
PolisanTheEasyNick Jun 17, 2024
52fd351
svg: change fill color depending on theme
PolisanTheEasyNick Jun 17, 2024
1e5d25c
feat: ablity to manually set fill color
PolisanTheEasyNick Jun 17, 2024
74e3902
feat: save icons into vesktop cache
PolisanTheEasyNick Jun 18, 2024
e899b16
Merge branch 'main' into tray-icon
PolisanTheEasyNick Jun 19, 2024
775ad30
feat: ability to disable icons rewriting
PolisanTheEasyNick Jun 19, 2024
42ecdb1
fix: force regenerate icons if can't open
PolisanTheEasyNick Jun 19, 2024
89ed267
refactor: use paths instead of filenames
PolisanTheEasyNick Jun 19, 2024
d943d44
Merge branch 'main' into tray-icon
PolisanTheEasyNick Jun 19, 2024
4146fd1
fix: add pencil icon/tray setting css classes
Jun 19, 2024
871054f
ux: condense customize tray settings
Jun 19, 2024
85f427f
ui: modal for picking custom tray icons
PolisanTheEasyNick Jun 20, 2024
9553660
ui: fix delimeters
PolisanTheEasyNick Jun 20, 2024
490b8c8
feat: ability to set custom icons
PolisanTheEasyNick Jun 20, 2024
12e9e61
mac: resize icons to 16x16
PolisanTheEasyNick Jun 20, 2024
f1c210e
feat: overrides by each icon type
PolisanTheEasyNick Jun 20, 2024
f759fc0
ui: changed to fit Discord
PolisanTheEasyNick Jun 20, 2024
1ddd933
ui: icon namings at right side
PolisanTheEasyNick Jun 21, 2024
c725352
feat: notification badge at tray
PolisanTheEasyNick Jun 21, 2024
d47582b
feat: custom badges colors
PolisanTheEasyNick Jun 21, 2024
ccbb094
fix: check for luminance only for badge
PolisanTheEasyNick Jun 21, 2024
b775af8
fix: reset button
PolisanTheEasyNick Jun 21, 2024
8bbe16b
feat: do not show count of notifs
PolisanTheEasyNick Jun 21, 2024
cb7363d
fix: show badge only for unreads
PolisanTheEasyNick Jun 21, 2024
79835b2
feat: accent color support for linix
PolisanTheEasyNick Jun 21, 2024
183a388
feat: choose icon from .svg
PolisanTheEasyNick Jun 22, 2024
4e9636a
fix: generate only one icon on error
PolisanTheEasyNick Jun 22, 2024
bb2e495
css: remove unused classes
PolisanTheEasyNick Jun 22, 2024
842f9b7
some clear-ups
PolisanTheEasyNick Jun 23, 2024
68daf93
fix: default tray when stream stopped
PolisanTheEasyNick Jun 28, 2024
05c15e9
fix: speak icon when stream with sound started
PolisanTheEasyNick Jul 1, 2024
9521c65
badge: show mentions instead of unreads
PolisanTheEasyNick Jul 1, 2024
9b631e6
fix: proper follow "Notification Badge" setting
PolisanTheEasyNick Jul 1, 2024
42c1cc0
Reword and restructure tray settings
Covkie Jul 1, 2024
fccbb7f
feat: dynamic change tray bg color on theme change
PolisanTheEasyNick Jul 2, 2024
e82b52e
Merge branch 'main' into tray-icon
Vendicated Jul 4, 2024
01fbcff
Merge branch 'main' into tray-icon
PolisanTheEasyNick Jul 5, 2024
679b0b7
fixes for vesktop update
PolisanTheEasyNick Jul 5, 2024
467134e
feat: add ability to use dynamic accent color
PolisanTheEasyNick Jul 7, 2024
ba98e8d
fix: add setting
PolisanTheEasyNick Jul 7, 2024
37cd4e9
fix: accent color for badge
PolisanTheEasyNick Jul 7, 2024
810be4e
Merge branch 'main' into tray-icon
PolisanTheEasyNick Jul 24, 2024
d379f96
Merge branch 'main' into tray-icon
PolisanTheEasyNick Aug 18, 2024
066afb8
ref: use dbus-native for getting accent color
PolisanTheEasyNick Aug 18, 2024
267b0a0
fix: update pnpm-lock
PolisanTheEasyNick Aug 18, 2024
a014cf5
fix exploded tests
PolisanTheEasyNick Aug 18, 2024
0571807
fix: show custom icon on startup initially
PolisanTheEasyNick Aug 24, 2024
a76f1de
Merge branch 'main' into tray-icon
PolisanTheEasyNick Aug 24, 2024
e86b31c
Merge branch 'main' into tray-icon
PolisanTheEasyNick Sep 3, 2024
60d0f2c
fix: svg scaling
PolisanTheEasyNick Sep 3, 2024
1416750
Merge branch 'main' into tray-icon
PolisanTheEasyNick Oct 3, 2024
a7ae0eb
Merge branch 'main' into tray-icon
PolisanTheEasyNick Oct 15, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 14 additions & 5 deletions src/main/appBadge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@

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<number, NativeImage>();
function loadBadge(index: number) {
const cached = imgCache.get(index);
Expand All @@ -19,13 +22,17 @@ function loadBadge(index: number) {
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;
app.setBadgeCount(count);
// commented out lines are temp to be replaced by #686
// if (count === -1) count = 0;
// app.setBadgeCount(count);

break;
case "darwin":
if (count === 0) {
Expand All @@ -36,15 +43,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] {
Expand Down
4 changes: 3 additions & 1 deletion src/main/firstLaunch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { ICON_PATH, VIEW_DIR } from "shared/paths";

import { autoStart } from "./autoStart";
import { DATA_DIR } from "./constants";
import { createWindows } from "./mainWindow";
import { createWindows, getAccentColor } from "./mainWindow";
import { Settings, State } from "./settings";
import { makeLinksOpenExternally } from "./utils/makeLinksOpenExternally";

Expand Down Expand Up @@ -48,6 +48,8 @@ export function createFirstLaunchTour() {
Settings.store.minimizeToTray = data.minimizeToTray;
Settings.store.discordBranch = data.discordBranch;
Settings.store.arRPC = data.richPresence;
Settings.store.tray = true;
Settings.store.trayColor = getAccentColor()?.slice(1);

if (data.autoStart) autoStart.enable();

Expand Down
22 changes: 21 additions & 1 deletion src/main/ipc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,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 } from "./mainWindow";
import { getAccentColor, mainWin } from "./mainWindow";
import { Settings } from "./settings";
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";
Expand Down Expand Up @@ -149,3 +158,14 @@ watch(
mainWin?.webContents.postMessage("VencordThemeUpdate", void 0);
})
);

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, () => getAccentColor());
handle(IpcEvents.CREATE_TRAY_ICON_RESPONSE, (_, iconName, dataURL, isCustomIcon, isSvg) =>
createTrayIcon(iconName, dataURL, isCustomIcon, isSvg)
);
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));
43 changes: 41 additions & 2 deletions src/main/mainWindow.ts
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
* Copyright (c) 2023 Vendicated and Vencord contributors
*/

import { execFileSync } from "child_process";
import {
app,
BrowserWindow,
Expand All @@ -14,16 +15,17 @@ import {
nativeTheme,
screen,
session,
systemPreferences,
Tray
} from "electron";
import { rm } from "fs/promises";
import { join } from "path";
import { IpcEvents } from "shared/IpcEvents";
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";

import { ICON_PATH } from "../shared/paths";
import { createAboutWindow } from "./about";
import { initArRPC } from "./arrpc";
import {
Expand All @@ -38,12 +40,13 @@ import {
} from "./constants";
import { Settings, State, VencordSettings } from "./settings";
import { createSplashWindow } from "./splash";
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();

Expand Down Expand Up @@ -124,6 +127,7 @@ function initTray(win: BrowserWindow) {
]);

tray = new Tray(ICON_PATH);
setTrayIcon("icon");
tray.setToolTip("Vesktop");
tray.setContextMenu(trayMenu);
tray.on("click", onTrayClick);
Expand Down Expand Up @@ -501,3 +505,38 @@ export async function createWindows() {

initArRPC();
}

export function getAccentColor() {
if (process.platform === "linux") {
var accentColor = execFileSync("gdbus", [
"call",
"--session",
"--dest",
"org.freedesktop.portal.Desktop",
"--object-path",
"/org/freedesktop/portal/desktop",
"--method",
"org.freedesktop.portal.Settings.Read",
"org.freedesktop.appearance",
"accent-color"
]);
const rgbMatch = accentColor.toString().match(/\((\d+\.\d+),\s*(\d+\.\d+),\s*(\d+\.\d+)\)/);

if (rgbMatch) {
const r = parseFloat(rgbMatch[1]);
const g = parseFloat(rgbMatch[2]);
const b = parseFloat(rgbMatch[3]);

const r255 = Math.round(r * 255);
const g255 = Math.round(g * 255);
const b255 = Math.round(b * 255);

const toHex = (value: number) => value.toString(16).padStart(2, "0");
const hexColor = `#${toHex(r255)}${toHex(g255)}${toHex(b255)}`;
return hexColor;
}
return "";
} else {
return `#${systemPreferences.getAccentColor?.() || ""}`;
}
}
187 changes: 187 additions & 0 deletions src/main/tray.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
/*
* 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 { dialog, NativeImage, nativeImage } from "electron";
import { mkdirSync, readFileSync, writeFileSync } from "fs";
import { readFile } from "fs/promises";
import { join } from "path";
import { IpcEvents } from "shared/IpcEvents";
import { BADGE_DIR, ICONS_DIR, STATIC_DIR } from "shared/paths";

import { lastBadgeCount } from "./appBadge";
import { mainWin, tray } 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 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 > 0) {
PolisanTheEasyNick marked this conversation as resolved.
Show resolved Hide resolved
var trayImage: NativeImage;
if (isCustomIcon("icon")) {
trayImage = nativeImage.createFromPath(join(ICONS_DIR, "icon_custom.png"));
} else {
trayImage = nativeImage.createFromPath(join(ICONS_DIR, "icon.png"));
}

if (trayImage.isEmpty()) {
const iconKey = statusToSettingsKey[iconName as keyof typeof statusToSettingsKey];
Settings.store[iconKey] = false;
generateTrayIcons("icon");
return;
}

const badgeSvg = readFileSync(join(BADGE_DIR, `badge.svg`), "utf8");
// and send IPC call to renderer to add badge to icon
mainWin.webContents.send(IpcEvents.ADD_BADGE_TO_ICON, trayImage.toDataURL(), badgeSvg);
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(iconName);
return;
}
} else trayImage = nativeImage.createFromPath(join(ICONS_DIR, iconName + ".png"));
if (trayImage.isEmpty()) {
generateTrayIcons(iconName);
return;
}
if (process.platform === "darwin") {
trayImage = trayImage.resize({ width: 16, height: 16 });
}
tray.setImage(trayImage);
} catch (error) {
console.log("Error: ", error, "Regenerating tray icon.");
generateTrayIcons(iconName);
}
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)) {
iconName = "icon";
return readFile(join(STATIC_DIR, "icon.png"));
}
return readFile(join(STATIC_DIR, iconName + ".svg"), "utf8");
}

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)) {
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 for", iconName, ". Regenerating.");
generateTrayIcons(iconName);
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,
isCustomIcon: boolean = false,
isSvg: 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,/, "");
if (isCustomIcon) {
const img = nativeImage.createFromDataURL(iconDataURL).resize({ width: 128, height: 128 });
if (isSvg) writeFileSync(join(ICONS_DIR, iconName + "_custom.png"), iconDataURL, "base64");
else writeFileSync(join(ICONS_DIR, iconName + "_custom.png"), img.toPNG());
} else {
writeFileSync(join(ICONS_DIR, iconName + ".png"), iconDataURL, "base64");
}
mainWin.webContents.send(IpcEvents.SET_CURRENT_VOICE_TRAY_ICON);
}

export async function generateTrayIcons(iconName: string = "") {
// this function generates tray icons as .png's in Vesktop cache for future use
mkdirSync(ICONS_DIR, { recursive: true });
const Icons = ["speaking", "muted", "deafened", "idle"];

const createMainIcon = () => {
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);
};

if (iconName) {
if (Icons.includes(iconName)) mainWin.webContents.send(IpcEvents.CREATE_TRAY_ICON_REQUEST, iconName);
else if (iconName === "icon") createMainIcon();
return;
}
for (const icon of Icons) {
mainWin.webContents.send(IpcEvents.CREATE_TRAY_ICON_REQUEST, icon);
}
createMainIcon();
}

export async function pickTrayIcon(iconName: string) {
const Icons = new Set(["speaking", "muted", "deafened", "idle", "icon"]);
if (!Icons.has(iconName)) return;

const res = await dialog.showOpenDialog(mainWin!, {
properties: ["openFile"],
filters: [{ name: "Image", extensions: ["png", "jpg", "svg"] }]
});
if (!res.filePaths.length) return "cancelled";
const dir = res.filePaths[0];
// add .svg !!
if (dir.split(".").pop() === "svg") {
mainWin.webContents.send(IpcEvents.CREATE_TRAY_ICON_REQUEST, iconName, readFileSync(dir, "utf-8"));
return "svg";
}
const image = nativeImage.createFromPath(dir);
if (image.isEmpty()) return "invalid";
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));
}
Loading
Loading