add equicord tray menu for desktop discord (#694)

This commit is contained in:
prism 2026-01-27 23:50:46 +01:00 • committed by GitHub
parent bfc9ecb5e7
commit d69256fb4d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 146 additions and 2 deletions

View file

@ -135,4 +135,9 @@ window.VencordNative = {
pluginHelpers: {} as any,
csp: {} as any,
tray: {
setUpdateState: NOOP,
onCheckUpdates: NOOP,
onRepair: NOOP,
},
};

View file

@ -36,14 +36,14 @@ import { createAndAppendStyle } from "@utils/css";
import { StartAt } from "@utils/types";
import { get as dsGet } from "./api/DataStore";
import { showNotice } from "./api/Notices";
import { popNotice, showNotice } from "./api/Notices";
import { NotificationData, showNotification } from "./api/Notifications";
import { initPluginManager, PMLogger, startAllPlugins } from "./api/PluginManager";
import { PlainSettings, Settings, SettingsStore } from "./api/Settings";
import { getCloudSettings, putCloudSettings, shouldCloudSync } from "./api/SettingsSync/cloudSync";
import { localStorage } from "./utils/localStorage";
import { relaunch } from "./utils/native";
import { checkForUpdates, update, UpdateLogger } from "./utils/updater";
import { checkForUpdates, isOutdated as getIsOutdated, update, UpdateLogger } from "./utils/updater";
import { onceReady } from "./webpack";
import { openUserSettingsPanel } from "./webpack/common";
import { patches } from "./webpack/patchWebpack";
@ -141,6 +141,7 @@ async function runUpdateCheck() {
try {
const isOutdated = await checkForUpdates();
if (IS_DISCORD_DESKTOP) VencordNative.tray.setUpdateState(isOutdated);
if (!isOutdated) return;
if (Settings.autoUpdate) {
@ -171,11 +172,45 @@ async function runUpdateCheck() {
}
}
function initTrayIpc() {
if (IS_WEB || IS_UPDATER_DISABLED) return;
VencordNative.tray.onCheckUpdates(async () => {
try {
const isOutdated = await checkForUpdates();
VencordNative.tray.setUpdateState(isOutdated);
if (isOutdated) {
showNotice("An Equicord update is available!", "View Update", () => openSettingsTabModal(UpdaterTab!));
} else {
showNotice("No updates available, you're on the latest version!", "OK", popNotice);
}
} catch (err) {
UpdateLogger.error("Failed to check for updates from tray", err);
showNotice("Failed to check for updates, check the console for more info", "OK", popNotice);
}
});
VencordNative.tray.onRepair(async () => {
try {
const res = await VencordNative.updater.rebuild();
if (!res.ok) throw res.error;
showNotice("Equicord has been repaired!", "Restart", relaunch);
} catch (err) {
UpdateLogger.error("Failed to repair Equicord", err);
}
});
VencordNative.tray.setUpdateState(getIsOutdated);
}
async function init() {
await onceReady;
startAllPlugins(StartAt.WebpackReady);
syncSettings();
initTrayIpc();
if (!IS_DEV && !IS_WEB && !IS_UPDATER_DISABLED) {
runUpdateCheck();

View file

@ -98,5 +98,11 @@ export default {
invoke<CspRequestResult>(IpcEvents.CSP_REQUEST_ADD_OVERRIDE, url, directives, callerName),
},
tray: {
setUpdateState: (available: boolean) => ipcRenderer.send(IpcEvents.SET_TRAY_UPDATE_STATE, available),
onCheckUpdates: (cb: () => void) => { ipcRenderer.on(IpcEvents.TRAY_CHECK_UPDATES, cb); },
onRepair: (cb: () => void) => { ipcRenderer.on(IpcEvents.TRAY_REPAIR_EQUICORD, cb); },
},
pluginHelpers: PluginHelpers
};

View file

@ -21,6 +21,7 @@ import electron, { app, BrowserWindowConstructorOptions, Menu } from "electron";
import { dirname, join } from "path";
import { RendererSettings } from "./settings";
import { patchTrayMenu } from "./trayMenu";
import { IS_VANILLA } from "./utils/constants";
console.log("[Equicord] Starting up...");
@ -40,6 +41,9 @@ app.setAppPath(asarPath);
if (!IS_VANILLA) {
const settings = RendererSettings.store;
patchTrayMenu();
// Repatch after host updates on Windows
if (process.platform === "win32") {
require("./patchWin32Updater");

90
src/main/trayMenu.ts Normal file
View file

@ -0,0 +1,90 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2026 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { IpcEvents } from "@shared/IpcEvents";
import { BrowserWindow, ipcMain, Menu, MenuItemConstructorOptions, shell } from "electron";
import { SETTINGS_DIR } from "./utils/constants";
let cachedUpdateAvailable = false;
ipcMain.on(IpcEvents.SET_TRAY_UPDATE_STATE, (_, available: boolean) => {
cachedUpdateAvailable = available;
});
function getMainWindow(): BrowserWindow | undefined {
return BrowserWindow.getFocusedWindow() ?? BrowserWindow.getAllWindows()[0];
}
function sendToRenderer(event: IpcEvents): void {
getMainWindow()?.webContents.send(event);
}
function findInsertIndex(template: MenuItemConstructorOptions[]): number {
const openIndex = template.findIndex(item => {
const label = item.label?.toLowerCase() ?? "";
return label.includes("open") || label.includes("show");
});
return openIndex !== -1 ? openIndex + 1 : 0;
}
function isTrayMenu(template: MenuItemConstructorOptions[]): boolean {
if (!template.length) return false;
const hasOpenOrShow = template.some(item => {
const label = item.label?.toLowerCase() ?? "";
return label.includes("open") || label.includes("show");
});
const hasQuit = template.some(item =>
item.label?.toLowerCase().includes("quit") || item.role === "quit"
);
const isNotAppMenu = !template.some(item =>
item.label === "&File" || item.label === "File" ||
item.label === "&Edit" || item.label === "Edit"
);
return hasOpenOrShow && hasQuit && isNotAppMenu;
}
function createEquicordMenuItems(): MenuItemConstructorOptions[] {
return [
{
label: "Equicord",
submenu: [
{
label: cachedUpdateAvailable ? "Update Equicord" : "Check for Updates",
click: () => sendToRenderer(IpcEvents.TRAY_CHECK_UPDATES)
},
{
label: "Repair Equicord",
click: () => sendToRenderer(IpcEvents.TRAY_REPAIR_EQUICORD)
},
{ type: "separator" },
{
label: "Open Settings Folder",
click: () => shell.openPath(SETTINGS_DIR)
}
]
},
{ type: "separator" }
];
}
export function patchTrayMenu(): void {
const originalBuildFromTemplate = Menu.buildFromTemplate;
Menu.buildFromTemplate = function (template: MenuItemConstructorOptions[]) {
if (isTrayMenu(template)) {
const insertIndex = findInsertIndex(template);
const equicordItems = createEquicordMenuItems();
template.splice(insertIndex, 0, ...equicordItems);
}
return originalBuildFromTemplate.call(this, template);
};
}

View file

@ -51,4 +51,8 @@ export const enum IpcEvents {
GET_RENDERER_CSS = "VencordGetRendererCss",
RENDERER_CSS_UPDATE = "VencordRendererCssUpdate",
PRELOAD_GET_RENDERER_JS = "VencordPreloadGetRendererJs",
SET_TRAY_UPDATE_STATE = "EquicordSetTrayUpdateState",
TRAY_REPAIR_EQUICORD = "EquicordTrayRepairEquicord",
TRAY_CHECK_UPDATES = "EquicordTrayCheckUpdates",
}