diff --git a/src/main/about.ts b/src/main/about.ts index a335b85..c15f2b8 100644 --- a/src/main/about.ts +++ b/src/main/about.ts @@ -7,7 +7,7 @@ import { app, BrowserWindow } from "electron"; import { readFileSync } from "fs"; import { join } from "path"; -import { ICON_PATH, STATIC_DIR } from "shared/paths"; +import { ICON_PATH, VIEW_DIR } from "shared/paths"; import { makeLinksOpenExternally } from "./utils/makeLinksOpenExternally"; @@ -20,7 +20,7 @@ export function createAboutWindow() { makeLinksOpenExternally(about); - const html = readFileSync(join(STATIC_DIR, "about.html"), "utf-8").replaceAll("%VERSION%", app.getVersion()); + const html = readFileSync(join(VIEW_DIR, "about.html"), "utf-8").replaceAll("%VERSION%", app.getVersion()); about.loadURL("data:text/html;charset=utf-8," + html); diff --git a/src/main/appBadge.ts b/src/main/appBadge.ts new file mode 100644 index 0000000..2887498 --- /dev/null +++ b/src/main/appBadge.ts @@ -0,0 +1,50 @@ +/* + * SPDX-License-Identifier: GPL-3.0 + * Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience + * Copyright (c) 2023 Vendicated and Vencord contributors + */ + +import { app, NativeImage, nativeImage } from "electron"; +import { join } from "path"; +import { BADGE_DIR } from "shared/paths"; + +const imgCache = new Map(); +function loadBadge(index: number) { + const cached = imgCache.get(index); + if (cached) return cached; + + const img = nativeImage.createFromPath(join(BADGE_DIR, `${index}.ico`)); + imgCache.set(index, img); + + return img; +} + +let lastIndex: null | number = -1; + +export function setBadgeCount(count: number) { + switch (process.platform) { + case "darwin": + case "linux": + if (count === -1) count = 0; + app.setBadgeCount(count); + break; + case "win32": + const [index, description] = getBadgeIndexAndDescription(count); + if (lastIndex === index) break; + + lastIndex = index; + + // circular import shenanigans + const { mainWin } = require("./mainWindow") as typeof import("./mainWindow"); + mainWin.setOverlayIcon(index === null ? null : loadBadge(index), description); + break; + } +} + +function getBadgeIndexAndDescription(count: number): [number | null, string] { + if (count === -1) return [11, "Unread Messages"]; + if (count === 0) return [null, "No Notifications"]; + + const index = Math.max(1, Math.min(count, 10)); + return [index, `${index} Notification`]; +} diff --git a/src/main/firstLaunch.ts b/src/main/firstLaunch.ts index e635fbd..f1b39dd 100644 --- a/src/main/firstLaunch.ts +++ b/src/main/firstLaunch.ts @@ -9,7 +9,7 @@ import { BrowserWindow } from "electron/main"; import { copyFileSync, mkdirSync, readdirSync } from "fs"; import { join } from "path"; import { SplashProps } from "shared/browserWinProperties"; -import { STATIC_DIR } from "shared/paths"; +import { VIEW_DIR } from "shared/paths"; import { autoStart } from "./autoStart"; import { DATA_DIR } from "./constants"; @@ -33,7 +33,7 @@ export function createFirstLaunchTour() { width: 550 }); - win.loadFile(join(STATIC_DIR, "first-launch.html")); + win.loadFile(join(VIEW_DIR, "first-launch.html")); win.webContents.addListener("console-message", (_e, _l, msg) => { if (msg === "cancel") return app.exit(); diff --git a/src/main/ipc.ts b/src/main/ipc.ts index 617cccf..138ad76 100644 --- a/src/main/ipc.ts +++ b/src/main/ipc.ts @@ -11,6 +11,7 @@ import { join } from "path"; import { debounce } from "shared/utils/debounce"; import { IpcEvents } from "../shared/IpcEvents"; +import { setBadgeCount } from "./appBadge"; import { autoStart } from "./autoStart"; import { VENCORD_FILES_DIR, VENCORD_QUICKCSS_FILE } from "./constants"; import { mainWin } from "./mainWindow"; @@ -89,6 +90,8 @@ ipcMain.handle(IpcEvents.SELECT_VENCORD_DIR, async () => { return dir; }); +ipcMain.handle(IpcEvents.SET_BADGE_COUNT, (_, count: number) => setBadgeCount(count)); + function readCss() { return readFile(VENCORD_QUICKCSS_FILE, "utf-8").catch(() => ""); } diff --git a/src/main/splash.ts b/src/main/splash.ts index b158a58..728c688 100644 --- a/src/main/splash.ts +++ b/src/main/splash.ts @@ -7,12 +7,12 @@ import { BrowserWindow } from "electron"; import { join } from "path"; import { SplashProps } from "shared/browserWinProperties"; -import { STATIC_DIR } from "shared/paths"; +import { VIEW_DIR } from "shared/paths"; export function createSplashWindow() { const splash = new BrowserWindow(SplashProps); - splash.loadFile(join(STATIC_DIR, "splash.html")); + splash.loadFile(join(VIEW_DIR, "splash.html")); return splash; } diff --git a/src/preload/VencordDesktopNative.ts b/src/preload/VencordDesktopNative.ts index 2e56324..d526fa8 100644 --- a/src/preload/VencordDesktopNative.ts +++ b/src/preload/VencordDesktopNative.ts @@ -13,7 +13,8 @@ import { invoke, sendSync } from "./typedIpcs"; export const VencordDesktopNative = { app: { relaunch: () => invoke(IpcEvents.RELAUNCH), - getVersion: () => sendSync(IpcEvents.GET_VERSION) + getVersion: () => sendSync(IpcEvents.GET_VERSION), + setBadgeCount: (count: number) => invoke(IpcEvents.SET_BADGE_COUNT, count) }, autostart: { isEnabled: () => sendSync(IpcEvents.AUTOSTART_ENABLED), diff --git a/src/renderer/appBadge.ts b/src/renderer/appBadge.ts new file mode 100644 index 0000000..ea40b60 --- /dev/null +++ b/src/renderer/appBadge.ts @@ -0,0 +1,43 @@ +/* + * SPDX-License-Identifier: GPL-3.0 + * Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience + * Copyright (c) 2023 Vendicated and Vencord contributors + */ + +import { filters, waitFor } from "@vencord/types/webpack"; +import { RelationshipStore } from "@vencord/types/webpack/common"; + +import { Settings } from "./settings"; + +let GuildReadStateStore: any; +let NotificationSettingsStore: any; + +export function setBadge() { + if (Settings.store.appBadge === false) return; + + const mentionCount = GuildReadStateStore.getTotalMentionCount(); + const pendingRequests = RelationshipStore.getPendingCount(); + const hasUnread = GuildReadStateStore.hasAnyUnread(); + const disableUnreadBadge = NotificationSettingsStore.getDisableUnreadBadge(); + + let totalCount = mentionCount + pendingRequests; + if (!totalCount && hasUnread && !disableUnreadBadge) totalCount = -1; + + VencordDesktopNative.app.setBadgeCount(totalCount); +} + +let toFind = 3; + +function waitForAndSubscribeToStore(name: string, cb?: (m: any) => void) { + waitFor(filters.byStoreName(name), store => { + cb?.(store); + store.addChangeListener(setBadge); + + toFind--; + if (toFind === 0) setBadge(); + }); +} + +waitForAndSubscribeToStore("GuildReadStateStore", store => (GuildReadStateStore = store)); +waitForAndSubscribeToStore("NotificationSettingsStore", store => (NotificationSettingsStore = store)); +waitForAndSubscribeToStore("RelationshipStore"); diff --git a/src/renderer/components/Settings.tsx b/src/renderer/components/Settings.tsx index 1c43b79..bf915b0 100644 --- a/src/renderer/components/Settings.tsx +++ b/src/renderer/components/Settings.tsx @@ -8,6 +8,7 @@ import "./settings.css"; import { Margins } from "@vencord/types/utils"; import { Button, Forms, Select, Switch, Text, useState } from "@vencord/types/webpack/common"; +import { setBadge } from "renderer/appBadge"; import { useSettings } from "renderer/settings"; export default function SettingsUi() { @@ -72,6 +73,18 @@ export default function SettingsUi() { Start With System + { + Settings.appBadge = v; + if (v) setBadge(); + else VencordDesktopNative.app.setBadgeCount(0); + }} + note="Show mention badge on the app icon" + > + Notification Badge + + {switches.map(([key, text, note, def, predicate]) => (