From b166253fe32f1167c85ae93f9188a9b7c5528e6b Mon Sep 17 00:00:00 2001 From: EastArctica Date: Fri, 21 Jul 2023 22:03:32 -0400 Subject: [PATCH 01/47] Add transparent window feature --- src/main/index.ts | 23 ++++++++++++++++++- .../coremods/settings/pages/General.tsx | 6 +++++ src/types/settings.ts | 2 ++ 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/src/main/index.ts b/src/main/index.ts index 4a2098744..25ba7ff6e 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -1,9 +1,24 @@ +import { readFileSync } from "fs"; import { dirname, join } from "path"; - import electron from "electron"; import type { RepluggedWebContents } from "../types"; import { CONFIG_PATHS } from "src/util.mjs"; +// TODO: This shouldn't be here, it should be in it's own module +// like src/main/ipc/settings.ts, except that's specifically for +// ipc transactions(which are async); So not there. Perhaps +// it should be in util? +const SETTINGS_DIR = CONFIG_PATHS.settings; +function readSettingsSync(namespace: string): Map { + try { + const data = readFileSync(join(SETTINGS_DIR, `${namespace}.json`), "utf8"); + return new Map(Object.entries(JSON.parse(data))); + } catch { + return new Map(); + } +} +const settings = readSettingsSync("dev.replugged.Settings"); + const electronPath = require.resolve("electron"); const discordPath = join(dirname(require.main!.filename), "..", "app.orig.asar"); // require.main!.filename = discordMain; @@ -50,6 +65,12 @@ class BrowserWindow extends electron.BrowserWindow { // Discord Client opts.webPreferences.preload = join(__dirname, "./preload.js"); // opts.webPreferences.contextIsolation = false; // shrug + + if (settings.get("transparentWindow")) { + opts.transparent = true; + opts.frame = process.platform === "win32" ? false : opts.frame; + delete opts.backgroundColor; + } } else { // Splash Screen on macOS (Host 0.0.262+) & Windows (Host 0.0.293 / 1.0.17+) // opts.webPreferences.preload = join(__dirname, './preloadSplash.js'); diff --git a/src/renderer/coremods/settings/pages/General.tsx b/src/renderer/coremods/settings/pages/General.tsx index 0d79a97e5..47047d1ea 100644 --- a/src/renderer/coremods/settings/pages/General.tsx +++ b/src/renderer/coremods/settings/pages/General.tsx @@ -113,6 +113,12 @@ export const General = (): React.ReactElement => { {Messages.REPLUGGED_SETTINGS_QUICKCSS_AUTO_APPLY} + + {Messages.REPLUGGED_SETTINGS_TRANSPARENT} + + = { @@ -21,4 +22,5 @@ export const defaultSettings: Partial = { badges: true, autoApplyQuickCss: false, showWelcomeNoticeOnOpen: true, + transparentWindow: false, }; From 0fb2d3bcdf4bebee250a1e9c2d28e106b9656b69 Mon Sep 17 00:00:00 2001 From: EastArctica Date: Tue, 22 Aug 2023 14:15:25 -0400 Subject: [PATCH 02/47] Require discord restart for transparency change --- src/renderer/coremods/settings/pages/General.tsx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/renderer/coremods/settings/pages/General.tsx b/src/renderer/coremods/settings/pages/General.tsx index cdbad1062..aeebb1188 100644 --- a/src/renderer/coremods/settings/pages/General.tsx +++ b/src/renderer/coremods/settings/pages/General.tsx @@ -55,6 +55,10 @@ export const General = (): React.ReactElement => { generalSettings, "reactDevTools", ); + const { value: transValue, onChange: transOnChange } = util.useSetting( + generalSettings, + "transparentWindow", + ); const [kKeys, setKKeys] = React.useState([]); @@ -175,7 +179,11 @@ export const General = (): React.ReactElement => { { + transOnChange(value); + restartModal(true); + }} note={Messages.REPLUGGED_SETTINGS_TRANSPARENT_DESC}> {Messages.REPLUGGED_SETTINGS_TRANSPARENT} From 71a57e879cfe761852f60a62a3b63dd4fbf04f84 Mon Sep 17 00:00:00 2001 From: EastArctica Date: Tue, 22 Aug 2023 16:48:29 -0400 Subject: [PATCH 03/47] Move transparency button --- .../coremods/settings/pages/General.tsx | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/renderer/coremods/settings/pages/General.tsx b/src/renderer/coremods/settings/pages/General.tsx index aeebb1188..3934aa880 100644 --- a/src/renderer/coremods/settings/pages/General.tsx +++ b/src/renderer/coremods/settings/pages/General.tsx @@ -118,6 +118,16 @@ export const General = (): React.ReactElement => { {Messages.REPLUGGED_SETTINGS_QUICKCSS_AUTO_APPLY} + { + transOnChange(value); + restartModal(true); + }} + note={Messages.REPLUGGED_SETTINGS_TRANSPARENT_DESC}> + {Messages.REPLUGGED_SETTINGS_TRANSPARENT} + + @@ -177,16 +187,6 @@ export const General = (): React.ReactElement => { }}> {Messages.REPLUGGED_SETTINGS_DEV_COMPANION} - - { - transOnChange(value); - restartModal(true); - }} - note={Messages.REPLUGGED_SETTINGS_TRANSPARENT_DESC}> - {Messages.REPLUGGED_SETTINGS_TRANSPARENT} - {/* Sleeping? Wake up. */} From 895e002e5f5139daedbb443e29bbf9c03e149ddb Mon Sep 17 00:00:00 2001 From: EastArctica Date: Tue, 22 Aug 2023 16:57:44 -0400 Subject: [PATCH 04/47] move readSettingsSync to src/util.mts --- src/main/index.ts | 17 +---------------- src/util.mts | 12 +++++++++++- 2 files changed, 12 insertions(+), 17 deletions(-) diff --git a/src/main/index.ts b/src/main/index.ts index 80c49d7c2..7b5b75ca0 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -1,25 +1,10 @@ -import { readFileSync } from "fs"; import { dirname, join } from "path"; import electron from "electron"; -import { CONFIG_PATHS } from "src/util.mjs"; +import { CONFIG_PATHS, readSettingsSync } from "src/util.mjs"; import type { RepluggedWebContents } from "../types"; import { getSetting } from "./ipc/settings"; -// TODO: This shouldn't be here, it should be in it's own module -// like src/main/ipc/settings.ts, except that's specifically for -// ipc transactions(which are async); So not there. Perhaps -// it should be in util? -const SETTINGS_DIR = CONFIG_PATHS.settings; -function readSettingsSync(namespace: string): Map { - try { - const data = readFileSync(join(SETTINGS_DIR, `${namespace}.json`), "utf8"); - return new Map(Object.entries(JSON.parse(data))); - } catch { - return new Map(); - } -} const settings = readSettingsSync("dev.replugged.Settings"); - const electronPath = require.resolve("electron"); const discordPath = join(dirname(require.main!.filename), "..", "app.orig.asar"); // require.main!.filename = discordMain; diff --git a/src/util.mts b/src/util.mts index 1fe485cd7..299b9d642 100644 --- a/src/util.mts +++ b/src/util.mts @@ -1,5 +1,5 @@ import { execSync } from "child_process"; -import { chownSync, existsSync, mkdirSync, statSync, writeFileSync } from "fs"; +import { chownSync, existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "fs"; import { join } from "path"; const REPLUGGED_FOLDER_NAME = "replugged"; @@ -79,3 +79,13 @@ if (!existsSync(QUICK_CSS_FILE)) { chownSync(QUICK_CSS_FILE, REAL_UID, REAL_GID); } } + +const SETTINGS_DIR = CONFIG_PATHS.settings; +export function readSettingsSync(namespace: string): Map { + try { + const data = readFileSync(join(SETTINGS_DIR, `${namespace}.json`), "utf8"); + return new Map(Object.entries(JSON.parse(data))); + } catch { + return new Map(); + } +} From 27e0e9fbd08a670896a2d915692e3648c13708cf Mon Sep 17 00:00:00 2001 From: EastArctica Date: Tue, 22 Aug 2023 23:03:49 -0400 Subject: [PATCH 05/47] At platform specific notices --- src/globals.d.ts | 3 +++ src/renderer/coremods/settings/pages/General.tsx | 13 ++++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/globals.d.ts b/src/globals.d.ts index f40d51cf5..f8ac22a83 100644 --- a/src/globals.d.ts +++ b/src/globals.d.ts @@ -37,6 +37,9 @@ declare global { paste: () => void; read: () => string; }; + process: { + platform: string; + }; }; export const _: typeof Lodash; diff --git a/src/renderer/coremods/settings/pages/General.tsx b/src/renderer/coremods/settings/pages/General.tsx index 3934aa880..d38713e0f 100644 --- a/src/renderer/coremods/settings/pages/General.tsx +++ b/src/renderer/coremods/settings/pages/General.tsx @@ -7,6 +7,7 @@ import { Divider, Flex, FormItem, + Notice, SwitchItem, Text, TextInput, @@ -118,13 +119,23 @@ export const General = (): React.ReactElement => { {Messages.REPLUGGED_SETTINGS_QUICKCSS_AUTO_APPLY} +
+ {(DiscordNative.process.platform === "linux" || + DiscordNative.process.platform === "win32") && ( + + {DiscordNative.process.platform === "linux" + ? Messages.REPLUGGED_SETTINGS_TRANSPARENT_ISSUES_LINUX.format() + : Messages.REPLUGGED_SETTINGS_TRANSPARENT_ISSUES_WINDOWS.format()} + + )} +
{ transOnChange(value); restartModal(true); }} - note={Messages.REPLUGGED_SETTINGS_TRANSPARENT_DESC}> + note={Messages.REPLUGGED_SETTINGS_TRANSPARENT_DESC.format()}> {Messages.REPLUGGED_SETTINGS_TRANSPARENT} From db444eb855af1ac7fe402f455948c09558b13faf Mon Sep 17 00:00:00 2001 From: East_Arctica Date: Wed, 23 Aug 2023 03:39:06 -0400 Subject: [PATCH 06/47] Fix transparency on Windows --- src/main/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/index.ts b/src/main/index.ts index 7b5b75ca0..a19681eb8 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -55,7 +55,7 @@ class BrowserWindow extends electron.BrowserWindow { if (settings.get("transparentWindow")) { opts.transparent = true; opts.frame = process.platform === "win32" ? false : opts.frame; - delete opts.backgroundColor; + opts.backgroundColor = "#00000000"; } } else { // Splash Screen on macOS (Host 0.0.262+) & Windows (Host 0.0.293 / 1.0.17+) From 4846af3869a3323cbfa1e8bd8ef8e6c52907d13f Mon Sep 17 00:00:00 2001 From: EastArctica Date: Thu, 24 Aug 2023 22:14:59 -0400 Subject: [PATCH 07/47] BrowserWindow rework --- src/main/index.ts | 59 ++++++++++++++++++++++++++++++++++------------- 1 file changed, 43 insertions(+), 16 deletions(-) diff --git a/src/main/index.ts b/src/main/index.ts index a19681eb8..c95f0dd64 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -22,6 +22,37 @@ Object.defineProperty(global, "appSettings", { configurable: true, }); +enum DiscordWindowType { + POP_OUT, + SPLASH_SCREEN, + OVERLAY, + DISCORD_CLIENT, +} + +function windowTypeFromOpts( + opts: electron.BrowserWindowConstructorOptions & { + webContents: electron.WebContents; + webPreferences: { + nativeWindowOpen: boolean; + }; + }, +): DiscordWindowType { + if (opts.webContents) { + return DiscordWindowType.POP_OUT; + } else if (opts.webPreferences?.nodeIntegration) { + return DiscordWindowType.SPLASH_SCREEN; + } else if (opts.webPreferences?.offscreen) { + return DiscordWindowType.OVERLAY; + } else if (opts.webPreferences?.preload) { + if (opts.webPreferences.nativeWindowOpen) { + return DiscordWindowType.DISCORD_CLIENT; + } else { + return DiscordWindowType.SPLASH_SCREEN; + } + } + return opts.webContents; +} + // This class has to be named "BrowserWindow" exactly // https://github.com/discord/electron/blob/13-x-y/lib/browser/api/browser-window.ts#L60-L62 // Thank you, Ven, for pointing this out! @@ -36,30 +67,26 @@ class BrowserWindow extends electron.BrowserWindow { ) { const originalPreload = opts.webPreferences.preload; - if (opts.webContents) { - // General purpose pop-outs used by Discord - } else if (opts.webPreferences?.nodeIntegration) { - // Splash Screen - // opts.webPreferences.preload = join(__dirname, './preloadSplash.js'); - } else if (opts.webPreferences?.offscreen) { - // Overlay - // originalPreload = opts.webPreferences.preload; - // opts.webPreferences.preload = join(__dirname, './preload.js'); - } else if (opts.webPreferences?.preload) { - // originalPreload = opts.webPreferences.preload; - if (opts.webPreferences.nativeWindowOpen) { - // Discord Client + const currentWindow = windowTypeFromOpts(opts); + + switch (currentWindow) { + case DiscordWindowType.DISCORD_CLIENT: { opts.webPreferences.preload = join(__dirname, "./preload.js"); - // opts.webPreferences.contextIsolation = false; // shrug if (settings.get("transparentWindow")) { opts.transparent = true; opts.frame = process.platform === "win32" ? false : opts.frame; opts.backgroundColor = "#00000000"; } - } else { - // Splash Screen on macOS (Host 0.0.262+) & Windows (Host 0.0.293 / 1.0.17+) + break; + } + case DiscordWindowType.SPLASH_SCREEN: { // opts.webPreferences.preload = join(__dirname, './preloadSplash.js'); + break; + } + case DiscordWindowType.OVERLAY: { + // opts.webPreferences.preload = join(__dirname, './preload.js'); + break; } } From c528f007f1872a7fa204547d6697438dac206740 Mon Sep 17 00:00:00 2001 From: EastArctica Date: Thu, 24 Aug 2023 22:15:24 -0400 Subject: [PATCH 08/47] MacOS Auto-Maximize --- src/main/index.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/main/index.ts b/src/main/index.ts index c95f0dd64..4f60e48e4 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -76,6 +76,7 @@ class BrowserWindow extends electron.BrowserWindow { if (settings.get("transparentWindow")) { opts.transparent = true; opts.frame = process.platform === "win32" ? false : opts.frame; + // TODO: Figure out what background color each OS needs. opts.backgroundColor = "#00000000"; } break; @@ -91,6 +92,20 @@ class BrowserWindow extends electron.BrowserWindow { } super(opts); + + if ( + currentWindow === DiscordWindowType.DISCORD_CLIENT && + settings.get("transparentWindow") && + process.platform === "darwin" + ) { + this.on("ready-to-show", () => { + // TODO: As far as I know, this is only needed on some macOS versions. + // This automatically maximizes the window on all displays the window is dragged to + this.maximize(); + this.setResizable(false); + }); + } + (this.webContents as RepluggedWebContents).originalPreload = originalPreload; } } From f496111cd1cb76d62e966adc7cd96a0e4dadc252 Mon Sep 17 00:00:00 2001 From: EastArctica Date: Fri, 21 Jul 2023 22:03:32 -0400 Subject: [PATCH 09/47] Add transparent window feature Require discord restart for transparency change Move transparency button move readSettingsSync to src/util.mts At platform specific notices Fix transparency on Windows BrowserWindow rework MacOS Auto-Maximize --- src/globals.d.ts | 3 + src/main/index.ts | 86 +++++++++++++++---- .../coremods/settings/pages/General.tsx | 31 +++++++ src/types/settings.ts | 2 + src/util.mts | 12 ++- 5 files changed, 114 insertions(+), 20 deletions(-) diff --git a/src/globals.d.ts b/src/globals.d.ts index f40d51cf5..f8ac22a83 100644 --- a/src/globals.d.ts +++ b/src/globals.d.ts @@ -37,6 +37,9 @@ declare global { paste: () => void; read: () => string; }; + process: { + platform: string; + }; }; export const _: typeof Lodash; diff --git a/src/main/index.ts b/src/main/index.ts index acbb2dc45..2ed18523e 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -1,10 +1,10 @@ import { dirname, join } from "path"; - import electron from "electron"; -import { CONFIG_PATHS } from "src/util.mjs"; +import { CONFIG_PATHS, readSettingsSync } from "src/util.mjs"; import type { RepluggedWebContents } from "../types"; import { getSetting } from "./ipc/settings"; +const settings = readSettingsSync("dev.replugged.Settings"); const electronPath = require.resolve("electron"); const discordPath = join(dirname(require.main!.filename), "..", "app.orig.asar"); // require.main!.filename = discordMain; @@ -22,6 +22,37 @@ Object.defineProperty(global, "appSettings", { configurable: true, }); +enum DiscordWindowType { + POP_OUT, + SPLASH_SCREEN, + OVERLAY, + DISCORD_CLIENT, +} + +function windowTypeFromOpts( + opts: electron.BrowserWindowConstructorOptions & { + webContents: electron.WebContents; + webPreferences: { + nativeWindowOpen: boolean; + }; + }, +): DiscordWindowType { + if (opts.webContents) { + return DiscordWindowType.POP_OUT; + } else if (opts.webPreferences?.nodeIntegration) { + return DiscordWindowType.SPLASH_SCREEN; + } else if (opts.webPreferences?.offscreen) { + return DiscordWindowType.OVERLAY; + } else if (opts.webPreferences?.preload) { + if (opts.webPreferences.nativeWindowOpen) { + return DiscordWindowType.DISCORD_CLIENT; + } else { + return DiscordWindowType.SPLASH_SCREEN; + } + } + return opts.webContents; +} + // This class has to be named "BrowserWindow" exactly // https://github.com/discord/electron/blob/13-x-y/lib/browser/api/browser-window.ts#L60-L62 // Thank you, Ven, for pointing this out! @@ -36,28 +67,45 @@ class BrowserWindow extends electron.BrowserWindow { ) { const originalPreload = opts.webPreferences?.preload; - if (opts.webContents) { - // General purpose pop-outs used by Discord - } else if (opts.webPreferences?.nodeIntegration) { - // Splash Screen - // opts.webPreferences.preload = join(__dirname, './preloadSplash.js'); - } else if (opts.webPreferences?.offscreen) { - // Overlay - // originalPreload = opts.webPreferences.preload; - // opts.webPreferences.preload = join(__dirname, './preload.js'); - } else if (opts.webPreferences?.preload) { - // originalPreload = opts.webPreferences.preload; - if (opts.webPreferences.nativeWindowOpen) { - // Discord Client - opts.webPreferences.preload = join(__dirname, "./preload.js"); - // opts.webPreferences.contextIsolation = false; // shrug - } else { - // Splash Screen on macOS (Host 0.0.262+) & Windows (Host 0.0.293 / 1.0.17+) + const currentWindow = windowTypeFromOpts(opts); + + switch (currentWindow) { + case DiscordWindowType.DISCORD_CLIENT: { opts.webPreferences.preload = join(__dirname, "./preload.js"); + + if (settings.get("transparentWindow")) { + opts.transparent = true; + opts.frame = process.platform === "win32" ? false : opts.frame; + // TODO: Figure out what background color each OS needs. + opts.backgroundColor = "#00000000"; + } + break; + } + case DiscordWindowType.SPLASH_SCREEN: { + // opts.webPreferences.preload = join(__dirname, "./preloadSplash.js"); + break; + } + case DiscordWindowType.OVERLAY: { + // opts.webPreferences.preload = join(__dirname, "./preload.js"); + break; } } super(opts); + + if ( + currentWindow === DiscordWindowType.DISCORD_CLIENT && + settings.get("transparentWindow") && + process.platform === "darwin" + ) { + this.on("ready-to-show", () => { + // TODO: As far as I know, this is only needed on some macOS versions. + // This automatically maximizes the window on all displays the window is dragged to + this.maximize(); + this.setResizable(false); + }); + } + (this.webContents as RepluggedWebContents).originalPreload = originalPreload; } } diff --git a/src/renderer/coremods/settings/pages/General.tsx b/src/renderer/coremods/settings/pages/General.tsx index daf6165f8..8f2f123c1 100644 --- a/src/renderer/coremods/settings/pages/General.tsx +++ b/src/renderer/coremods/settings/pages/General.tsx @@ -7,6 +7,7 @@ import { Divider, Flex, FormItem, + Notice, SwitchItem, Text, TextInput, @@ -55,6 +56,10 @@ export const General = (): React.ReactElement => { generalSettings, "reactDevTools", ); + const { value: transValue, onChange: transOnChange } = util.useSetting( + generalSettings, + "transparentWindow", + ); const [kKeys, setKKeys] = React.useState([]); @@ -114,6 +119,26 @@ export const General = (): React.ReactElement => { {Messages.REPLUGGED_SETTINGS_QUICKCSS_AUTO_APPLY} +
+ {(DiscordNative.process.platform === "linux" || + DiscordNative.process.platform === "win32") && ( + + {DiscordNative.process.platform === "linux" + ? Messages.REPLUGGED_SETTINGS_TRANSPARENT_ISSUES_LINUX.format() + : Messages.REPLUGGED_SETTINGS_TRANSPARENT_ISSUES_WINDOWS.format()} + + )} +
+ { + transOnChange(value); + restartModal(true); + }} + note={Messages.REPLUGGED_SETTINGS_TRANSPARENT_DESC.format()}> + {Messages.REPLUGGED_SETTINGS_TRANSPARENT} + + @@ -165,6 +190,12 @@ export const General = (): React.ReactElement => { {Messages.REPLUGGED_SETTINGS_REACT_DEVTOOLS} + + {Messages.REPLUGGED_SETTINGS_TRANSPARENT} + + ; diff --git a/src/util.mts b/src/util.mts index 99a1f8a85..ce621290b 100644 --- a/src/util.mts +++ b/src/util.mts @@ -1,6 +1,6 @@ import esbuild from "esbuild"; import { execSync } from "child_process"; -import { chownSync, existsSync, mkdirSync, statSync, writeFileSync } from "fs"; +import { chownSync, existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "fs"; import path, { join } from "path"; import chalk from "chalk"; @@ -145,3 +145,13 @@ export const logBuildPlugin: esbuild.Plugin = { }); }, }; + +const SETTINGS_DIR = CONFIG_PATHS.settings; +export function readSettingsSync(namespace: string): Map { + try { + const data = readFileSync(join(SETTINGS_DIR, `${namespace}.json`), "utf8"); + return new Map(Object.entries(JSON.parse(data))); + } catch { + return new Map(); + } +} From f57657aee8a4580b342eb0de7ffeb131487c1ed0 Mon Sep 17 00:00:00 2001 From: EastArctica Date: Wed, 31 Jan 2024 15:01:42 -0500 Subject: [PATCH 10/47] I'm impressively bad at rebasing --- src/main/index.ts | 31 ------------------------------- src/types/settings.ts | 1 - 2 files changed, 32 deletions(-) diff --git a/src/main/index.ts b/src/main/index.ts index 1618a7a7e..697dc7986 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -53,37 +53,6 @@ function windowTypeFromOpts( return opts.webContents; } -enum DiscordWindowType { - POP_OUT, - SPLASH_SCREEN, - OVERLAY, - DISCORD_CLIENT, -} - -function windowTypeFromOpts( - opts: electron.BrowserWindowConstructorOptions & { - webContents: electron.WebContents; - webPreferences: { - nativeWindowOpen: boolean; - }; - }, -): DiscordWindowType { - if (opts.webContents) { - return DiscordWindowType.POP_OUT; - } else if (opts.webPreferences?.nodeIntegration) { - return DiscordWindowType.SPLASH_SCREEN; - } else if (opts.webPreferences?.offscreen) { - return DiscordWindowType.OVERLAY; - } else if (opts.webPreferences?.preload) { - if (opts.webPreferences.nativeWindowOpen) { - return DiscordWindowType.DISCORD_CLIENT; - } else { - return DiscordWindowType.SPLASH_SCREEN; - } - } - return opts.webContents; -} - // This class has to be named "BrowserWindow" exactly // https://github.com/discord/electron/blob/13-x-y/lib/browser/api/browser-window.ts#L60-L62 // Thank you, Ven, for pointing this out! diff --git a/src/types/settings.ts b/src/types/settings.ts index 70d3b5d35..127da0c77 100644 --- a/src/types/settings.ts +++ b/src/types/settings.ts @@ -25,5 +25,4 @@ export const defaultSettings = { transparentWindow: false, reactDevTools: false, addonEmbeds: true, - transparentWindow: false, } satisfies Partial; From f0b6a475f5ecfee6e4919f9eb9b9612d51626a6e Mon Sep 17 00:00:00 2001 From: EastArctica Date: Sun, 4 Feb 2024 15:33:42 -0500 Subject: [PATCH 11/47] Add vibe for transparency --- package.json | 3 +- pnpm-lock.yaml | 246 ++++++++++++++++++++++++++++++++++++++++------ scripts/build.mts | 5 +- src/main/index.ts | 31 ++---- src/vibe.node | Bin 0 -> 188928 bytes 5 files changed, 229 insertions(+), 56 deletions(-) create mode 100644 src/vibe.node diff --git a/package.json b/package.json index 3568361c5..bb3cb6692 100644 --- a/package.json +++ b/package.json @@ -82,6 +82,7 @@ "@electron/asar": "^3.2.7", "@lezer/highlight": "^1.1.6", "@octokit/rest": "^20.0.2", + "@pyke/vibe": "^0.4.0", "adm-zip": "^0.5.10", "chalk": "^5.3.0", "codemirror": "^6.0.1", @@ -106,4 +107,4 @@ "semver@<7.5.2": ">=7.5.2" } } -} +} \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0d4ad3d15..8fe294107 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -29,6 +29,9 @@ dependencies: '@octokit/rest': specifier: ^20.0.2 version: 20.0.2 + '@pyke/vibe': + specifier: ^0.4.0 + version: 0.4.0(electron@28.2.1) adm-zip: specifier: ^0.5.10 version: 0.5.10 @@ -582,6 +585,23 @@ packages: minimatch: 3.1.2 dev: false + /@electron/get@2.0.3: + resolution: {integrity: sha512-Qkzpg2s9GnVV2I2BjRksUi43U5e6+zaQMcjoJy0C+C5oxaKl+fmckGDQFtRpZpZV0NQekuZZ+tGz7EA9TVnQtQ==} + engines: {node: '>=12'} + dependencies: + debug: 4.3.4 + env-paths: 2.2.1 + fs-extra: 8.1.0 + got: 11.8.6 + progress: 2.0.3 + semver: 7.5.4 + sumchecker: 3.0.1 + optionalDependencies: + global-agent: 3.0.0 + transitivePeerDependencies: + - supports-color + dev: false + /@esbuild/android-arm64@0.18.20: resolution: {integrity: sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==} engines: {node: '>=12'} @@ -1209,6 +1229,16 @@ packages: '@pnpm/network.ca-file': 1.0.2 config-chain: 1.1.13 + /@pyke/vibe@0.4.0(electron@28.2.1): + resolution: {integrity: sha512-EUfoGqglwpuTuL3/lBq1eWzOL1HE9XwkDXvsq8mwSSGrw3lFCzKL2dyD/NFBiobjqIqN93ut45gRS51LbE0gpw==} + requiresBuild: true + peerDependencies: + electron: '>=11.0' + dependencies: + cargo-cp-artifact: 0.1.8 + electron: 28.2.1 + dev: false + /@samverschueren/stream-to-observable@0.3.1(rxjs@6.6.7): resolution: {integrity: sha512-c/qwwcHyafOQuVQJj0IlBjf5yYgBI7YPJ77k4fOJYesb41jio65eaJODRUmfYKhTOFBrIZ66kgvGPlNbjuoRdQ==} engines: {node: '>=6'} @@ -1230,7 +1260,6 @@ packages: /@sindresorhus/is@4.6.0: resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==} engines: {node: '>=10'} - dev: true /@sindresorhus/is@5.6.0: resolution: {integrity: sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==} @@ -1241,7 +1270,6 @@ packages: engines: {node: '>=10'} dependencies: defer-to-connect: 2.0.1 - dev: true /@szmarczak/http-timer@5.0.1: resolution: {integrity: sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==} @@ -1262,7 +1290,6 @@ packages: '@types/keyv': 3.1.4 '@types/node': 18.18.6 '@types/responselike': 1.0.2 - dev: true /@types/configstore@6.0.1: resolution: {integrity: sha512-ML2U2xzN6PwCh1I6QgWI3TdzS7CRLsv5DSCLX4/Wfg+f30JF60WgS9ZbVOOrTk+N67WTCSUKm6Q7k6M/BjbXQw==} @@ -1287,7 +1314,6 @@ packages: resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==} dependencies: '@types/node': 18.18.6 - dev: true /@types/lodash@4.14.200: resolution: {integrity: sha512-YI/M/4HRImtNf3pJgbF+W6FrXovqj+T+/HpENLTooK9PnkacBsDpeP3IpHab40CClUfhNmdM2WTNP2sa2dni5Q==} @@ -1342,7 +1368,6 @@ packages: resolution: {integrity: sha512-/4YQT5Kp6HxUDb4yhRkm0bJ7TbjvTddqX7PZ5hz6qV3pxSo72f/6YPRo+Mu2DU307tm9IioO69l7uAwn5XNcFA==} dependencies: '@types/node': 18.18.6 - dev: true /@types/scheduler@0.16.5: resolution: {integrity: sha512-s/FPdYRmZR8SjLWGMCuax7r3qCWQw9QKHzXVukAuuIJkXkDRwp+Pu5LMIVFi0Fxbav35WURicYr8u1QsoybnQw==} @@ -1382,6 +1407,14 @@ packages: '@types/yargs-parser': 21.0.2 dev: true + /@types/yauzl@2.10.3: + resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} + requiresBuild: true + dependencies: + '@types/node': 18.18.6 + dev: false + optional: true + /@typescript-eslint/eslint-plugin@6.8.0(@typescript-eslint/parser@6.8.0)(eslint@8.51.0)(typescript@5.2.2): resolution: {integrity: sha512-GosF4238Tkes2SHPQ1i8f6rMtG6zlKwMEB0abqSJ3Npvos+doIlc/ATG+vX1G9coDF3Ex78zM3heXHLyWEwLUw==} engines: {node: ^16.0.0 || >=18.0.0} @@ -1770,6 +1803,12 @@ packages: readable-stream: 3.6.2 dev: true + /boolean@3.2.0: + resolution: {integrity: sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==} + requiresBuild: true + dev: false + optional: true + /boxen@7.1.1: resolution: {integrity: sha512-2hCgjEmP8YLWQ130n2FerGv7rYpfBmnmp9Uy2Le1vge6X3gZIfSmEzP5QTDElFxcvVcXlEn8Aq6MU/PZygIOog==} engines: {node: '>=14.16'} @@ -1808,6 +1847,10 @@ packages: dependencies: fill-range: 7.0.1 + /buffer-crc32@0.2.13: + resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} + dev: false + /buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} dev: false @@ -1833,7 +1876,6 @@ packages: /cacheable-lookup@5.0.4: resolution: {integrity: sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==} engines: {node: '>=10.6.0'} - dev: true /cacheable-lookup@7.0.0: resolution: {integrity: sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==} @@ -1862,7 +1904,6 @@ packages: lowercase-keys: 2.0.0 normalize-url: 6.1.0 responselike: 2.0.1 - dev: true /call-bind@1.0.2: resolution: {integrity: sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==} @@ -1885,6 +1926,11 @@ packages: resolution: {integrity: sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==} engines: {node: '>=14.16'} + /cargo-cp-artifact@0.1.8: + resolution: {integrity: sha512-3j4DaoTrsCD1MRkTF2Soacii0Nx7UHCce0EwUf4fHnggwiE4fbmF2AbnfzayR36DF8KGadfh7M/Yfy625kgPlA==} + hasBin: true + dev: false + /chalk-template@1.1.0: resolution: {integrity: sha512-T2VJbcDuZQ0Tb2EWwSotMPJjgpy1/tGee1BTpUNsGZ/qgNjV2t7Mvu+d4600U564nbLesN1x2dPL+xii174Ekg==} engines: {node: '>=14.16'} @@ -2020,7 +2066,6 @@ packages: resolution: {integrity: sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==} dependencies: mimic-response: 1.0.1 - dev: true /clone@1.0.4: resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} @@ -2301,7 +2346,6 @@ packages: optional: true dependencies: ms: 2.1.2 - dev: true /decompress-response@6.0.0: resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} @@ -2352,7 +2396,6 @@ packages: get-intrinsic: 1.2.1 gopd: 1.0.1 has-property-descriptors: 1.0.0 - dev: true /define-lazy-prop@3.0.0: resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==} @@ -2366,7 +2409,6 @@ packages: define-data-property: 1.1.1 has-property-descriptors: 1.0.0 object-keys: 1.1.1 - dev: true /del@7.1.0: resolution: {integrity: sha512-v2KyNk7efxhlyHpjEvfyxaAihKKK0nWCuf6ZtqZcFFpQRG0bJ12Qsr0RpvsICMjAAZ8DOVCxrlqpxISlMHC4Kg==} @@ -2386,6 +2428,12 @@ packages: resolution: {integrity: sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==} dev: false + /detect-node@2.1.0: + resolution: {integrity: sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==} + requiresBuild: true + dev: false + optional: true + /dir-glob@3.0.1: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} @@ -2430,6 +2478,19 @@ packages: /eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + /electron@28.2.1: + resolution: {integrity: sha512-wlzXf+OvOiVlBf9dcSeMMf7Q+N6DG+wtgFbMK0sA/JpIJcdosRbLMQwLg/LTwNVKIbmayqFLDp4FmmFkEMhbYA==} + engines: {node: '>= 12.20.55'} + hasBin: true + requiresBuild: true + dependencies: + '@electron/get': 2.0.3 + '@types/node': 18.18.6 + extract-zip: 2.0.1 + transitivePeerDependencies: + - supports-color + dev: false + /elegant-spinner@1.0.1: resolution: {integrity: sha512-B+ZM+RXvRqQaAmkMlO/oSe5nMUOaUnyfGYCEHoR8wrXsZR2mA0XVibsxV1bvTwxdRWah1PkQqso2EzhILGHtEQ==} engines: {node: '>=0.10.0'} @@ -2445,7 +2506,11 @@ packages: resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} dependencies: once: 1.4.0 - dev: true + + /env-paths@2.2.1: + resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} + engines: {node: '>=6'} + dev: false /error-ex@1.3.2: resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} @@ -2541,6 +2606,12 @@ packages: is-symbol: 1.0.4 dev: true + /es6-error@4.1.1: + resolution: {integrity: sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==} + requiresBuild: true + dev: false + optional: true + /esbuild-sass-plugin@2.16.0(esbuild@0.19.5): resolution: {integrity: sha512-mGCe9MxNYvZ+j77Q/QFO+rwUGA36mojDXkOhtVmoyz1zwYbMaNrtVrmXwwYDleS/UMKTNU3kXuiTtPiAD3K+Pw==} peerDependencies: @@ -2628,7 +2699,6 @@ packages: /escape-string-regexp@4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} - dev: true /escape-string-regexp@5.0.0: resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} @@ -2845,6 +2915,20 @@ packages: tmp: 0.0.33 dev: true + /extract-zip@2.0.1: + resolution: {integrity: sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==} + engines: {node: '>= 10.17.0'} + hasBin: true + dependencies: + debug: 4.3.4 + get-stream: 5.2.0 + yauzl: 2.10.0 + optionalDependencies: + '@types/yauzl': 2.10.3 + transitivePeerDependencies: + - supports-color + dev: false + /fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} dev: true @@ -2883,6 +2967,12 @@ packages: reusify: 1.0.4 dev: true + /fd-slicer@1.1.0: + resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==} + dependencies: + pend: 1.2.0 + dev: false + /fetch-blob@3.2.0: resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==} engines: {node: ^12.20 || >= 14.13} @@ -2995,6 +3085,15 @@ packages: fetch-blob: 3.2.0 dev: false + /fs-extra@8.1.0: + resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==} + engines: {node: '>=6 <7 || >=8'} + dependencies: + graceful-fs: 4.2.11 + jsonfile: 4.0.0 + universalify: 0.1.2 + dev: false + /fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} @@ -3008,7 +3107,6 @@ packages: /function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} - dev: true /function.prototype.name@1.1.6: resolution: {integrity: sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==} @@ -3041,7 +3139,6 @@ packages: has: 1.0.4 has-proto: 1.0.1 has-symbols: 1.0.3 - dev: true /get-stdin@9.0.0: resolution: {integrity: sha512-dVKBjfWisLAicarI2Sf+JuBE/DghV4UzNAVe9yhEJuzeREd3JhOTE9cUaJTeSa77fsbQUK3pcOpJfM59+VKZaA==} @@ -3053,7 +3150,6 @@ packages: engines: {node: '>=8'} dependencies: pump: 3.0.0 - dev: true /get-stream@6.0.1: resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} @@ -3100,6 +3196,20 @@ packages: once: 1.4.0 path-is-absolute: 1.0.1 + /global-agent@3.0.0: + resolution: {integrity: sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==} + engines: {node: '>=10.0'} + requiresBuild: true + dependencies: + boolean: 3.2.0 + es6-error: 4.1.1 + matcher: 3.0.0 + roarr: 2.15.4 + semver: 7.5.4 + serialize-error: 7.0.1 + dev: false + optional: true + /global-dirs@3.0.1: resolution: {integrity: sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==} engines: {node: '>=10'} @@ -3118,7 +3228,6 @@ packages: engines: {node: '>= 0.4'} dependencies: define-properties: 1.2.1 - dev: true /globby@11.1.0: resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} @@ -3147,7 +3256,6 @@ packages: resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} dependencies: get-intrinsic: 1.2.1 - dev: true /got@11.8.6: resolution: {integrity: sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==} @@ -3164,7 +3272,6 @@ packages: lowercase-keys: 2.0.0 p-cancelable: 2.1.1 responselike: 2.0.1 - dev: true /got@12.6.1: resolution: {integrity: sha512-mThBblvlAF1d4O5oqyvN+ZxLAYwIJK7bpMxgYqPD9okW0C3qm5FFn7k811QrcuEBwaogR3ngOFoCfs6mRv7teQ==} @@ -3222,17 +3329,14 @@ packages: resolution: {integrity: sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==} dependencies: get-intrinsic: 1.2.1 - dev: true /has-proto@1.0.1: resolution: {integrity: sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==} engines: {node: '>= 0.4'} - dev: true /has-symbols@1.0.3: resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} engines: {node: '>= 0.4'} - dev: true /has-tostringtag@1.0.0: resolution: {integrity: sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==} @@ -3272,7 +3376,6 @@ packages: dependencies: quick-lru: 5.1.1 resolve-alpn: 1.2.1 - dev: true /http2-wrapper@2.2.0: resolution: {integrity: sha512-kZB0wxMo0sh1PehyjJUWRFEd99KC5TLjZ2cULC4f9iqJBAmKQQXEICjxl5iPJRwP40dpeHFqqhm7tYCvODpqpQ==} @@ -3810,10 +3913,22 @@ packages: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} dev: true + /json-stringify-safe@5.0.1: + resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} + requiresBuild: true + dev: false + optional: true + /jsonc-parser@3.2.0: resolution: {integrity: sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==} dev: true + /jsonfile@4.0.0: + resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} + optionalDependencies: + graceful-fs: 4.2.11 + dev: false + /jsx-ast-utils@3.3.5: resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} engines: {node: '>=4.0'} @@ -3990,7 +4105,6 @@ packages: /lowercase-keys@2.0.0: resolution: {integrity: sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==} engines: {node: '>=8'} - dev: true /lowercase-keys@3.0.0: resolution: {integrity: sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==} @@ -4017,6 +4131,15 @@ packages: hasBin: true dev: true + /matcher@3.0.0: + resolution: {integrity: sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==} + engines: {node: '>=10'} + requiresBuild: true + dependencies: + escape-string-regexp: 4.0.0 + dev: false + optional: true + /meow@12.1.1: resolution: {integrity: sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw==} engines: {node: '>=16.10'} @@ -4057,7 +4180,6 @@ packages: /mimic-response@1.0.1: resolution: {integrity: sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==} engines: {node: '>=4'} - dev: true /mimic-response@3.1.0: resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} @@ -4088,7 +4210,6 @@ packages: /ms@2.1.2: resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} - dev: true /mute-stream@0.0.7: resolution: {integrity: sha512-r65nCZhrbXXb6dXOACihYApHw2Q6pV0M3V0PSxd74N0+D8nzAdEAITq2oAjA1jVnKI+tGvEBUpqiMh0+rW6zDQ==} @@ -4158,7 +4279,6 @@ packages: /normalize-url@6.1.0: resolution: {integrity: sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==} engines: {node: '>=10'} - dev: true /normalize-url@8.0.0: resolution: {integrity: sha512-uVFpKhj5MheNBJRTiMZ9pE/7hD1QTeEvugSJW/OmLzAp78PB5O6adfMNTvmfKhXBkvCzC+rqifWcVYpGFwTjnw==} @@ -4258,7 +4378,6 @@ packages: /object-keys@1.1.1: resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} engines: {node: '>= 0.4'} - dev: true /object.assign@4.1.4: resolution: {integrity: sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==} @@ -4391,7 +4510,6 @@ packages: /p-cancelable@2.1.1: resolution: {integrity: sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==} engines: {node: '>=8'} - dev: true /p-cancelable@3.0.0: resolution: {integrity: sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==} @@ -4554,6 +4672,10 @@ packages: engines: {node: '>=8'} dev: true + /pend@1.2.0: + resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} + dev: false + /picomatch@2.3.1: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} @@ -4586,7 +4708,6 @@ packages: /progress@2.0.3: resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} engines: {node: '>=0.4.0'} - dev: true /prompts@2.4.2: resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} @@ -4612,7 +4733,6 @@ packages: dependencies: end-of-stream: 1.4.4 once: 1.4.0 - dev: true /punycode@2.3.0: resolution: {integrity: sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==} @@ -4792,7 +4912,6 @@ packages: resolution: {integrity: sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==} dependencies: lowercase-keys: 2.0.0 - dev: true /responselike@3.0.0: resolution: {integrity: sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==} @@ -4828,6 +4947,20 @@ packages: glob: 7.2.3 dev: true + /roarr@2.15.4: + resolution: {integrity: sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==} + engines: {node: '>=8.0'} + requiresBuild: true + dependencies: + boolean: 3.2.0 + detect-node: 2.1.0 + globalthis: 1.0.3 + json-stringify-safe: 5.0.1 + semver-compare: 1.0.0 + sprintf-js: 1.1.3 + dev: false + optional: true + /run-applescript@5.0.0: resolution: {integrity: sha512-XcT5rBksx1QdIhlFOCtgZkB99ZEouFZ1E2Kc2LHqNW13U3/74YGdkQRmThTwxy4QIyookibDKYZOPqX//6BlAg==} engines: {node: '>=12'} @@ -4905,6 +5038,12 @@ packages: engines: {node: '>=12'} dev: true + /semver-compare@1.0.0: + resolution: {integrity: sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==} + requiresBuild: true + dev: false + optional: true + /semver-diff@4.0.0: resolution: {integrity: sha512-0Ju4+6A8iOnpL/Thra7dZsSlOHYAHIeMxfhWQRI1/VLcT3WDBZKKtQt/QkBOsiIN9ZpuvHE6cGZ0x4glCMmfiA==} engines: {node: '>=12'} @@ -4918,6 +5057,15 @@ packages: dependencies: lru-cache: 6.0.0 + /serialize-error@7.0.1: + resolution: {integrity: sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==} + engines: {node: '>=10'} + requiresBuild: true + dependencies: + type-fest: 0.13.1 + dev: false + optional: true + /set-function-name@2.0.1: resolution: {integrity: sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA==} engines: {node: '>= 0.4'} @@ -5023,6 +5171,12 @@ packages: resolution: {integrity: sha512-eWN+LnM3GR6gPu35WxNgbGl8rmY1AEmoMDvL/QD6zYmPWgywxWqJWNdLGT+ke8dKNWrcYgYjPpG5gbTfghP8rw==} dev: true + /sprintf-js@1.1.3: + resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==} + requiresBuild: true + dev: false + optional: true + /standalone-electron-types@1.0.0: resolution: {integrity: sha512-0HOi/tlTz3mjWhsAz4uRbpQcHMZ+ifj1JzWW9nugykOHClBBG77ps8QinrzX1eow4Iw2pnC+RFaSYRgufF4BOg==} dependencies: @@ -5162,6 +5316,15 @@ packages: /style-mod@4.1.0: resolution: {integrity: sha512-Ca5ib8HrFn+f+0n4N4ScTIA9iTOQ7MaGS1ylHcoVqW9J7w2w8PzN6g9gKmTYgGEBH8e120+RCmhpje6jC5uGWA==} + /sumchecker@3.0.1: + resolution: {integrity: sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==} + engines: {node: '>= 8.0'} + dependencies: + debug: 4.3.4 + transitivePeerDependencies: + - supports-color + dev: false + /supports-color@2.0.0: resolution: {integrity: sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==} engines: {node: '>=0.8.0'} @@ -5276,6 +5439,13 @@ packages: prelude-ls: 1.2.1 dev: true + /type-fest@0.13.1: + resolution: {integrity: sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==} + engines: {node: '>=10'} + requiresBuild: true + dev: false + optional: true + /type-fest@0.20.2: resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} engines: {node: '>=10'} @@ -5403,6 +5573,11 @@ packages: resolution: {integrity: sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w==} dev: false + /universalify@0.1.2: + resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} + engines: {node: '>= 4.0.0'} + dev: false + /untildify@4.0.0: resolution: {integrity: sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==} engines: {node: '>=8'} @@ -5647,6 +5822,13 @@ packages: yargs-parser: 21.1.1 dev: false + /yauzl@2.10.0: + resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==} + dependencies: + buffer-crc32: 0.2.13 + fd-slicer: 1.1.0 + dev: false + /yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} diff --git a/scripts/build.mts b/scripts/build.mts index a879f74c0..c53ce716b 100644 --- a/scripts/build.mts +++ b/scripts/build.mts @@ -84,6 +84,9 @@ const contexts = await Promise.all([ target: `node${NODE_VERSION}`, outfile: `${distDir}/main.js`, external: ["electron", "original-fs"], + loader: { + ".node": "file", + } }), // Preload esbuild.context({ @@ -109,7 +112,7 @@ await Promise.all( if (watch) { await context.watch(); } else { - await context.rebuild().catch(() => {}); + await context.rebuild().catch(() => { }); context.dispose(); } }), diff --git a/src/main/index.ts b/src/main/index.ts index 697dc7986..01d703c6d 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -3,6 +3,9 @@ import electron from "electron"; import { CONFIG_PATHS, readSettingsSync } from "src/util.mjs"; import type { RepluggedWebContents } from "../types"; import { getSetting } from "./ipc/settings"; +import vibePath from "../vibe.node"; +const vibe = require(vibePath) as unknown as typeof import('@pyke/vibe'); +vibe.setup(electron.app); const settings = readSettingsSync("dev.replugged.Settings"); const electronPath = require.resolve("electron"); @@ -74,8 +77,10 @@ class BrowserWindow extends electron.BrowserWindow { opts.webPreferences.preload = join(__dirname, "./preload.js"); if (settings.get("transparentWindow")) { - opts.transparent = true; - opts.frame = process.platform === "win32" ? false : opts.frame; + opts.show = false; + // Menu bar needs to be remade -_- + opts.autoHideMenuBar = true; + // opts.frame = process.platform === "win32" ? false : opts.frame; // TODO: Figure out what background color each OS needs. opts.backgroundColor = "#00000000"; } @@ -95,28 +100,10 @@ class BrowserWindow extends electron.BrowserWindow { if ( currentWindow === DiscordWindowType.DISCORD_CLIENT && - settings.get("transparentWindow") && - process.platform === "darwin" + settings.get("transparentWindow") ) { this.on("ready-to-show", () => { - // TODO: As far as I know, this is only needed on some macOS versions. - // This automatically maximizes the window on all displays the window is dragged to - this.maximize(); - this.setResizable(false); - }); - } - - - if ( - currentWindow === DiscordWindowType.DISCORD_CLIENT && - settings.get("transparentWindow") && - process.platform === "darwin" - ) { - this.on("ready-to-show", () => { - // TODO: As far as I know, this is only needed on some macOS versions. - // This automatically maximizes the window on all displays the window is dragged to - this.maximize(); - this.setResizable(false); + vibe.applyEffect(this, 'acrylic'); }); } diff --git a/src/vibe.node b/src/vibe.node new file mode 100644 index 0000000000000000000000000000000000000000..dd6fbe432e55948d87dd742361755e6731b73ada GIT binary patch literal 188928 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

|`4PY0E2snfjCu(=V;8!;2 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 Date: Sat, 10 Feb 2024 16:19:20 -0500 Subject: [PATCH 12/47] Use unified acrylic and auto set theme + background color --- src/main/index.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/index.ts b/src/main/index.ts index 01d703c6d..c45fb6e07 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -103,7 +103,9 @@ class BrowserWindow extends electron.BrowserWindow { settings.get("transparentWindow") ) { this.on("ready-to-show", () => { - vibe.applyEffect(this, 'acrylic'); + vibe.applyEffect(this, 'unified-acrylic'); + vibe.forceTheme(this, 'dark'); + this.setBackgroundColor("#00000000"); }); } From bfe5f72f0b7710bf6218885694339a836d96f24c Mon Sep 17 00:00:00 2001 From: East_Arctica Date: Fri, 16 Feb 2024 20:43:53 -0500 Subject: [PATCH 13/47] Add transparency IPC --- src/main/ipc/transparency.ts | 4 ++++ src/preload.ts | 5 +++++ src/types/index.ts | 1 + 3 files changed, 10 insertions(+) create mode 100644 src/main/ipc/transparency.ts diff --git a/src/main/ipc/transparency.ts b/src/main/ipc/transparency.ts new file mode 100644 index 000000000..1ba757bdb --- /dev/null +++ b/src/main/ipc/transparency.ts @@ -0,0 +1,4 @@ +import { ipcMain } from "electron"; +import { RepluggedIpcChannels } from "../../types"; + +ipcMain.handle(RepluggedIpcChannels.APPLY_TRANSPARENCY_EFFECT, () => {}); diff --git a/src/preload.ts b/src/preload.ts index 1a611af16..e68c7ae08 100644 --- a/src/preload.ts +++ b/src/preload.ts @@ -108,6 +108,11 @@ const RepluggedNative = { ipcRenderer.invoke(RepluggedIpcChannels.DOWNLOAD_REACT_DEVTOOLS), }, + transparency: { + applyEffect: (): Promise => + ipcRenderer.invoke(RepluggedIpcChannels.APPLY_TRANSPARENCY_EFFECT), + }, + getVersion: () => version, // eslint-disable-next-line @typescript-eslint/no-unused-vars diff --git a/src/types/index.ts b/src/types/index.ts index e614b2c92..127b92ebf 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -33,6 +33,7 @@ export enum RepluggedIpcChannels { OPEN_QUICKCSS_FOLDER = "REPLUGGED_OPEN_QUICKCSS_FOLDER", GET_REPLUGGED_VERSION = "REPLUGGED_GET_REPLUGGED_VERSION", DOWNLOAD_REACT_DEVTOOLS = "REPLUGGED_DOWNLOAD_REACT_DEVTOOLS", + APPLY_TRANSPARENCY_EFFECT = "REPLUGGED_APPLY_TRANSPARENCY_EFFECT", } export interface RepluggedAnnouncement { From 445e4dfa01aaa11aa65d59903034dfe9ccf20730 Mon Sep 17 00:00:00 2001 From: East_Arctica Date: Fri, 16 Feb 2024 20:44:38 -0500 Subject: [PATCH 14/47] Linux support(ish) & Clean up errors --- cspell.json | 3 ++- package.json | 6 +++-- pnpm-lock.yaml | 27 ++++++++++++++++------ src/main/index.ts | 57 +++++++++++++++++++++++++---------------------- 4 files changed, 56 insertions(+), 37 deletions(-) diff --git a/cspell.json b/cspell.json index 2e4895344..606ef70fd 100644 --- a/cspell.json +++ b/cspell.json @@ -67,7 +67,8 @@ "Vendicated", "Webauthn", "weblate", - "XLARGE" + "XLARGE", + "pyke" ], "ignoreWords": [], "import": [], diff --git a/package.json b/package.json index e6f49c0c2..ed95569b6 100644 --- a/package.json +++ b/package.json @@ -82,7 +82,6 @@ "@electron/asar": "^3.2.7", "@lezer/highlight": "^1.1.6", "@octokit/rest": "^20.0.2", - "@pyke/vibe": "^0.4.0", "adm-zip": "^0.5.10", "chalk": "^5.3.0", "codemirror": "^6.0.1", @@ -99,6 +98,9 @@ "yargs": "^17.7.2", "zod": "^3.22.4" }, + "optionalDependencies": { + "@pyke/vibe": "^0.4.0" + }, "bin": { "replugged": "bin.mjs" }, @@ -107,4 +109,4 @@ "semver@<7.5.2": ">=7.5.2" } } -} \ No newline at end of file +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f160dc7ab..17e0f5620 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -29,9 +29,6 @@ dependencies: '@octokit/rest': specifier: ^20.0.2 version: 20.0.2 - '@pyke/vibe': - specifier: ^0.4.0 - version: 0.4.0(electron@28.2.1) adm-zip: specifier: ^0.5.10 version: 0.5.10 @@ -78,6 +75,11 @@ dependencies: specifier: ^3.22.4 version: 3.22.4 +optionalDependencies: + '@pyke/vibe': + specifier: ^0.4.0 + version: 0.4.0(electron@28.2.1) + devDependencies: '@types/adm-zip': specifier: ^0.5.3 @@ -601,6 +603,7 @@ packages: transitivePeerDependencies: - supports-color dev: false + optional: true /@esbuild/android-arm64@0.18.20: resolution: {integrity: sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==} @@ -1238,6 +1241,7 @@ packages: cargo-cp-artifact: 0.1.8 electron: 28.2.1 dev: false + optional: true /@samverschueren/stream-to-observable@0.3.1(rxjs@6.6.7): resolution: {integrity: sha512-c/qwwcHyafOQuVQJj0IlBjf5yYgBI7YPJ77k4fOJYesb41jio65eaJODRUmfYKhTOFBrIZ66kgvGPlNbjuoRdQ==} @@ -1850,10 +1854,7 @@ packages: /buffer-crc32@0.2.13: resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} dev: false - - /buffer-from@1.1.2: - resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} - dev: false + optional: true /buffer@5.7.1: resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} @@ -1929,7 +1930,9 @@ packages: /cargo-cp-artifact@0.1.8: resolution: {integrity: sha512-3j4DaoTrsCD1MRkTF2Soacii0Nx7UHCce0EwUf4fHnggwiE4fbmF2AbnfzayR36DF8KGadfh7M/Yfy625kgPlA==} hasBin: true + requiresBuild: true dev: false + optional: true /chalk-template@1.1.0: resolution: {integrity: sha512-T2VJbcDuZQ0Tb2EWwSotMPJjgpy1/tGee1BTpUNsGZ/qgNjV2t7Mvu+d4600U564nbLesN1x2dPL+xii174Ekg==} @@ -2490,6 +2493,7 @@ packages: transitivePeerDependencies: - supports-color dev: false + optional: true /elegant-spinner@1.0.1: resolution: {integrity: sha512-B+ZM+RXvRqQaAmkMlO/oSe5nMUOaUnyfGYCEHoR8wrXsZR2mA0XVibsxV1bvTwxdRWah1PkQqso2EzhILGHtEQ==} @@ -2511,6 +2515,7 @@ packages: resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} engines: {node: '>=6'} dev: false + optional: true /error-ex@1.3.2: resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} @@ -2928,6 +2933,7 @@ packages: transitivePeerDependencies: - supports-color dev: false + optional: true /fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -2972,6 +2978,7 @@ packages: dependencies: pend: 1.2.0 dev: false + optional: true /fetch-blob@3.2.0: resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==} @@ -3093,6 +3100,7 @@ packages: jsonfile: 4.0.0 universalify: 0.1.2 dev: false + optional: true /fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} @@ -3928,6 +3936,7 @@ packages: optionalDependencies: graceful-fs: 4.2.11 dev: false + optional: true /jsx-ast-utils@3.3.5: resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} @@ -4675,6 +4684,7 @@ packages: /pend@1.2.0: resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} dev: false + optional: true /picomatch@2.3.1: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} @@ -5312,6 +5322,7 @@ packages: transitivePeerDependencies: - supports-color dev: false + optional: true /supports-color@2.0.0: resolution: {integrity: sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==} @@ -5565,6 +5576,7 @@ packages: resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} engines: {node: '>= 4.0.0'} dev: false + optional: true /untildify@4.0.0: resolution: {integrity: sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==} @@ -5816,6 +5828,7 @@ packages: buffer-crc32: 0.2.13 fd-slicer: 1.1.0 dev: false + optional: true /yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} diff --git a/src/main/index.ts b/src/main/index.ts index c45fb6e07..604b24e49 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -3,9 +3,14 @@ import electron from "electron"; import { CONFIG_PATHS, readSettingsSync } from "src/util.mjs"; import type { RepluggedWebContents } from "../types"; import { getSetting } from "./ipc/settings"; +// @ts-expect-error Type defs are obtained through @pyke/vibe import vibePath from "../vibe.node"; -const vibe = require(vibePath) as unknown as typeof import('@pyke/vibe'); -vibe.setup(electron.app); + +let vibe: typeof import("@pyke/vibe"); +if (process.platform === "win32") { + vibe = require(vibePath) as unknown as typeof import("@pyke/vibe"); + vibe.setup(electron.app); +} const settings = readSettingsSync("dev.replugged.Settings"); const electronPath = require.resolve("electron"); @@ -26,20 +31,21 @@ Object.defineProperty(global, "appSettings", { }); enum DiscordWindowType { + UNKNOWN, POP_OUT, SPLASH_SCREEN, OVERLAY, DISCORD_CLIENT, } -function windowTypeFromOpts( - opts: electron.BrowserWindowConstructorOptions & { - webContents: electron.WebContents; - webPreferences: { - nativeWindowOpen: boolean; - }; - }, -): DiscordWindowType { +type InternalBrowserWindowConstructorOptions = electron.BrowserWindowConstructorOptions & { + webContents?: electron.WebContents; + webPreferences?: { + nativeWindowOpen: boolean; + }; +}; + +function windowTypeFromOpts(opts: InternalBrowserWindowConstructorOptions): DiscordWindowType { if (opts.webContents) { return DiscordWindowType.POP_OUT; } else if (opts.webPreferences?.nodeIntegration) { @@ -53,28 +59,22 @@ function windowTypeFromOpts( return DiscordWindowType.SPLASH_SCREEN; } } - return opts.webContents; + + return DiscordWindowType.UNKNOWN; } // This class has to be named "BrowserWindow" exactly // https://github.com/discord/electron/blob/13-x-y/lib/browser/api/browser-window.ts#L60-L62 // Thank you, Ven, for pointing this out! class BrowserWindow extends electron.BrowserWindow { - public constructor( - opts: electron.BrowserWindowConstructorOptions & { - webContents?: electron.WebContents; - webPreferences?: { - nativeWindowOpen: boolean; - }; - }, - ) { + public constructor(opts: InternalBrowserWindowConstructorOptions) { const originalPreload = opts.webPreferences?.preload; const currentWindow = windowTypeFromOpts(opts); switch (currentWindow) { case DiscordWindowType.DISCORD_CLIENT: { - opts.webPreferences.preload = join(__dirname, "./preload.js"); + opts.webPreferences!.preload = join(__dirname, "./preload.js"); if (settings.get("transparentWindow")) { opts.show = false; @@ -82,7 +82,11 @@ class BrowserWindow extends electron.BrowserWindow { opts.autoHideMenuBar = true; // opts.frame = process.platform === "win32" ? false : opts.frame; // TODO: Figure out what background color each OS needs. - opts.backgroundColor = "#00000000"; + opts.backgroundColor = "#0000000"; + + if (process.platform === "linux") { + opts.transparent = true; + } } break; } @@ -98,13 +102,12 @@ class BrowserWindow extends electron.BrowserWindow { super(opts); - if ( - currentWindow === DiscordWindowType.DISCORD_CLIENT && - settings.get("transparentWindow") - ) { + if (currentWindow === DiscordWindowType.DISCORD_CLIENT && settings.get("transparentWindow")) { this.on("ready-to-show", () => { - vibe.applyEffect(this, 'unified-acrylic'); - vibe.forceTheme(this, 'dark'); + if (process.platform === "win32") { + vibe.applyEffect(this, "unified-acrylic"); + vibe.forceTheme(this, "dark"); + } this.setBackgroundColor("#00000000"); }); } From cd70df7977fc03621edb7b2c2ed019256ac00339 Mon Sep 17 00:00:00 2001 From: East_Arctica Date: Fri, 16 Feb 2024 20:52:11 -0500 Subject: [PATCH 15/47] Fix Prettier --- scripts/build.mts | 4 ++-- src/renderer/apis/settings.ts | 4 ++-- src/renderer/coremods/commands/commands.ts | 8 ++++---- src/renderer/coremods/commands/index.ts | 4 ++-- src/renderer/coremods/settings/pages/Updater.tsx | 5 +++-- src/renderer/util.ts | 13 +++++++------ 6 files changed, 20 insertions(+), 18 deletions(-) diff --git a/scripts/build.mts b/scripts/build.mts index c53ce716b..4762514c7 100644 --- a/scripts/build.mts +++ b/scripts/build.mts @@ -86,7 +86,7 @@ const contexts = await Promise.all([ external: ["electron", "original-fs"], loader: { ".node": "file", - } + }, }), // Preload esbuild.context({ @@ -112,7 +112,7 @@ await Promise.all( if (watch) { await context.watch(); } else { - await context.rebuild().catch(() => { }); + await context.rebuild().catch(() => {}); context.dispose(); } }), diff --git a/src/renderer/apis/settings.ts b/src/renderer/apis/settings.ts index 93708dd52..2cdfd9a51 100644 --- a/src/renderer/apis/settings.ts +++ b/src/renderer/apis/settings.ts @@ -57,8 +57,8 @@ export class SettingsManager, D extends ke ): K extends D ? NonNullable : F extends null | undefined - ? T[K] | undefined - : NonNullable | F { + ? T[K] | undefined + : NonNullable | F { if (typeof this.#settings === "undefined") { throw new Error(`Settings not loaded for namespace ${this.namespace}`); } diff --git a/src/renderer/coremods/commands/commands.ts b/src/renderer/coremods/commands/commands.ts index 7665c1f12..7377fe0f0 100644 --- a/src/renderer/coremods/commands/commands.ts +++ b/src/renderer/coremods/commands/commands.ts @@ -351,8 +351,8 @@ export function loadCommands(): void { listType === "enabled" ? enabledString : listType === "disabled" - ? disabledString - : `${enabledString}\n\n${disabledString}`; + ? disabledString + : `${enabledString}\n\n${disabledString}`; return { send, @@ -383,8 +383,8 @@ export function loadCommands(): void { listType === "enabled" ? enabledString : listType === "disabled" - ? disabledString - : `${enabledString}\n\n${disabledString}`; + ? disabledString + : `${enabledString}\n\n${disabledString}`; return { send, diff --git a/src/renderer/coremods/commands/index.ts b/src/renderer/coremods/commands/index.ts index 3db259d1e..4836625d0 100644 --- a/src/renderer/coremods/commands/index.ts +++ b/src/renderer/coremods/commands/index.ts @@ -217,8 +217,8 @@ async function injectApplicationCommandSearchStore(): Promise { ); if (!commandAndSectionsArray.length) return; if ( - !commandAndSectionsArray.every((commandAndSection) => - res?.some((section) => section.id === commandAndSection.section.id), + !commandAndSectionsArray.every( + (commandAndSection) => res?.some((section) => section.id === commandAndSection.section.id), ) ) { const sectionsToAdd = commandAndSectionsArray diff --git a/src/renderer/coremods/settings/pages/Updater.tsx b/src/renderer/coremods/settings/pages/Updater.tsx index 9473d39c7..f56d2b97d 100644 --- a/src/renderer/coremods/settings/pages/Updater.tsx +++ b/src/renderer/coremods/settings/pages/Updater.tsx @@ -22,8 +22,9 @@ const logger = Logger.coremod("Settings:Updater"); export const Updater = (): React.ReactElement => { const [checking, setChecking] = React.useState(false); - const [updatesAvailable, setUpdatesAvailable] = - React.useState>(getAvailableUpdates()); + const [updatesAvailable, setUpdatesAvailable] = React.useState< + Array + >(getAvailableUpdates()); const [updatePromises, setUpdatePromises] = React.useState>>({}); const [didInstallAll, setDidInstallAll] = React.useState(false); const [lastChecked, setLastChecked] = useSettingArray(updaterSettings, "lastChecked"); diff --git a/src/renderer/util.ts b/src/renderer/util.ts index b086517fc..e3bd99c2b 100644 --- a/src/renderer/util.ts +++ b/src/renderer/util.ts @@ -197,8 +197,8 @@ export function useSetting< value: K extends D ? NonNullable : F extends null | undefined - ? T[K] | undefined - : NonNullable | F; + ? T[K] | undefined + : NonNullable | F; onChange: (newValue: ValType) => void; } { const initial = settings.get(key, fallback); @@ -237,8 +237,8 @@ export function useSettingArray< K extends D ? NonNullable : F extends null | undefined - ? T[K] | undefined - : NonNullable | F, + ? T[K] | undefined + : NonNullable | F, (newValue: ValType) => void, ] { const { value, onChange } = useSetting(settings, key, fallback); @@ -256,8 +256,9 @@ type UnionToIntersection = (U extends never ? never : (k: U) => void) extends type ObjectType = Record; -type ExtractObjectType = - O extends Array ? UnionToIntersection : never; +type ExtractObjectType = O extends Array + ? UnionToIntersection + : never; export function virtualMerge(...objects: O): ExtractObjectType { const fallback = {}; From 51c4d2dde60e958b91f693575808bce1c162a2fe Mon Sep 17 00:00:00 2001 From: East_Arctica Date: Wed, 21 Feb 2024 16:46:27 -0500 Subject: [PATCH 16/47] Vibrancy & win32 effect IPC --- src/main/ipc/index.ts | 1 + src/main/ipc/transparency.ts | 54 ++++++++++++++++++++++++++++++++++-- src/preload.ts | 15 ++++++++-- src/types/index.ts | 3 ++ 4 files changed, 69 insertions(+), 4 deletions(-) diff --git a/src/main/ipc/index.ts b/src/main/ipc/index.ts index c664f0cb4..f28b2e8e8 100644 --- a/src/main/ipc/index.ts +++ b/src/main/ipc/index.ts @@ -6,6 +6,7 @@ import "./settings"; import "./installer"; import "./i18n"; import "./react-devtools"; +import "./transparency"; import { RepluggedIpcChannels, type RepluggedWebContents } from "../../types"; ipcMain.on(RepluggedIpcChannels.GET_DISCORD_PRELOAD, (event) => { diff --git a/src/main/ipc/transparency.ts b/src/main/ipc/transparency.ts index 1ba757bdb..103e9a6ff 100644 --- a/src/main/ipc/transparency.ts +++ b/src/main/ipc/transparency.ts @@ -1,4 +1,54 @@ -import { ipcMain } from "electron"; +import { BrowserWindow, ipcMain } from "electron"; import { RepluggedIpcChannels } from "../../types"; +// @ts-expect-error Type defs are obtained through @pyke/vibe +import vibePath from "../../vibe.node"; -ipcMain.handle(RepluggedIpcChannels.APPLY_TRANSPARENCY_EFFECT, () => {}); +let vibe: typeof import("@pyke/vibe"); +if (process.platform === "win32") { + vibe = require(vibePath) as unknown as typeof import("@pyke/vibe"); +} + +let currentEffect: Parameters[1] | null = null; +ipcMain.handle( + RepluggedIpcChannels.GET_TRANSPARENCY_EFFECT, + (): Parameters[1] | null => { + if (DiscordNative.process.platform !== "win32") { + console.warn("GET_TRANSPARENCY_EFFECT only works on Windows"); + } + + return currentEffect; + }, +); + +ipcMain.handle( + RepluggedIpcChannels.APPLY_TRANSPARENCY_EFFECT, + (_, effect: Parameters[1]) => { + if (DiscordNative.process.platform !== "win32") { + console.warn("RepluggedNative.transparency.applyEffect only works on Windows"); + return; + } + + let windows = BrowserWindow.getAllWindows(); + console.log(windows); + // TODO: Is it a bad idea to apply this to all active windows? + windows.forEach((window) => vibe.applyEffect(window, effect)); + currentEffect = effect; + }, +); + +let currentVibrancy: Parameters[0] = null; +ipcMain.handle( + RepluggedIpcChannels.GET_VIBRANCY, + (): Parameters[0] => currentVibrancy, +); + +ipcMain.handle( + RepluggedIpcChannels.SET_VIBRANCY, + (_, vibrancy: Parameters[0]) => { + let windows = BrowserWindow.getAllWindows(); + + // TODO: Is it a bad idea to apply this to all active windows? + windows.forEach((window) => window.setVibrancy(vibrancy)); + currentVibrancy = vibrancy; + }, +); diff --git a/src/preload.ts b/src/preload.ts index e68c7ae08..32a5eb3fe 100644 --- a/src/preload.ts +++ b/src/preload.ts @@ -3,6 +3,7 @@ import { contextBridge, ipcRenderer, webFrame, + BrowserWindow, } from "electron"; import { RepluggedIpcChannels } from "./types"; @@ -17,6 +18,8 @@ import type { RepluggedTheme, RepluggedTranslations, } from "./types"; +// Note that this may ONLY be used for types. +import vibe from "@pyke/vibe"; let version = ""; void ipcRenderer.invoke(RepluggedIpcChannels.GET_REPLUGGED_VERSION).then((v) => { @@ -109,8 +112,16 @@ const RepluggedNative = { }, transparency: { - applyEffect: (): Promise => - ipcRenderer.invoke(RepluggedIpcChannels.APPLY_TRANSPARENCY_EFFECT), + getEffect: (): Promise[1]> => + ipcRenderer.invoke(RepluggedIpcChannels.GET_TRANSPARENCY_EFFECT), + applyEffect: (effect: Parameters[1]): Promise => + ipcRenderer.invoke(RepluggedIpcChannels.APPLY_TRANSPARENCY_EFFECT, effect), + getVibrancy: (): Promise[0]> => + ipcRenderer.invoke(RepluggedIpcChannels.GET_VIBRANCY), + setVibrancy: ( + vibrancy: Parameters[0], + ): Promise => ipcRenderer.invoke(RepluggedIpcChannels.SET_VIBRANCY, vibrancy), + // visualEffectState does not need to be implemented until https://github.com/electron/electron/issues/25513 is implemented. }, getVersion: () => version, diff --git a/src/types/index.ts b/src/types/index.ts index 127b92ebf..e9347f576 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -33,7 +33,10 @@ export enum RepluggedIpcChannels { OPEN_QUICKCSS_FOLDER = "REPLUGGED_OPEN_QUICKCSS_FOLDER", GET_REPLUGGED_VERSION = "REPLUGGED_GET_REPLUGGED_VERSION", DOWNLOAD_REACT_DEVTOOLS = "REPLUGGED_DOWNLOAD_REACT_DEVTOOLS", + GET_TRANSPARENCY_EFFECT = "REPLUGGED_GET_TRANSPARENCY_EFFECT", APPLY_TRANSPARENCY_EFFECT = "REPLUGGED_APPLY_TRANSPARENCY_EFFECT", + GET_VIBRANCY = "REPLUGGED_GET_VIBRANCY", + SET_VIBRANCY = "REPLUGGED_SET_VIBRANCY", } export interface RepluggedAnnouncement { From 90c0e39eaeb58c2bcf18b56aad925b870c8b34f9 Mon Sep 17 00:00:00 2001 From: East_Arctica Date: Wed, 21 Feb 2024 16:47:07 -0500 Subject: [PATCH 17/47] Transparency coremod --- src/renderer/coremods/transparency/index.ts | 67 +++++++++++++++++++++ src/renderer/managers/coremods.ts | 2 + 2 files changed, 69 insertions(+) create mode 100644 src/renderer/coremods/transparency/index.ts diff --git a/src/renderer/coremods/transparency/index.ts b/src/renderer/coremods/transparency/index.ts new file mode 100644 index 000000000..761ae8a8f --- /dev/null +++ b/src/renderer/coremods/transparency/index.ts @@ -0,0 +1,67 @@ +import { Logger } from "@replugged"; + +const logger = Logger.coremod("Transparency"); +let observer: MutationObserver; + +export function start(): void { + let html = document.body.parentElement!; + // RepluggedNative.transparency.applyEffect(); + observer = new MutationObserver(async (mutations) => { + let cssModified = false; + for (const mutation of mutations) { + if (mutation.target instanceof HTMLLinkElement && mutation.type === "attributes") { + cssModified = true; + break; + } + + if (mutation.type !== "childList") continue; + + // Check for both added or removed css(like) nodes + let changedNodes = Array.from(mutation.addedNodes).concat(Array.from(mutation.removedNodes)); + let cssLikeNodes = changedNodes.filter( + (node) => node instanceof HTMLStyleElement || node instanceof HTMLLinkElement, + ); + if (cssLikeNodes.length > 0) { + cssModified = true; + break; + } + } + + if (cssModified) { + switch (DiscordNative.process.platform) { + case "win32": { + const transparencyEffect = getComputedStyle(html).getPropertyValue("--window-win-blur"); + if (transparencyEffect === (await RepluggedNative.transparency.getEffect())) { + return; + } + + // @ts-expect-error TODO: Check if the vibrancy is valid? + await RepluggedNative.transparency.applyEffect(transparencyEffect); + break; + } + case "darwin": { + const vibrancy = getComputedStyle(html).getPropertyValue("--window-vibrancy"); + if (vibrancy === (await RepluggedNative.transparency.getVibrancy())) { + return; + } + + // @ts-expect-error TODO: Check if the vibrancy is valid? + await RepluggedNative.transparency.setVibrancy(vibrancy); + break; + } + } + } + }); + + observer.observe(html, { + subtree: true, + childList: true, + // To handle any instances where a link has it's href changed. + attributes: true, + attributeFilter: ["href"], + }); +} + +export function stop(): void { + observer.disconnect(); +} diff --git a/src/renderer/managers/coremods.ts b/src/renderer/managers/coremods.ts index cafd75fb7..fdc406cd2 100644 --- a/src/renderer/managers/coremods.ts +++ b/src/renderer/managers/coremods.ts @@ -34,6 +34,7 @@ export namespace coremods { export let watcher: Coremod; export let commands: Coremod; export let welcome: Coremod; + export let transparency: Coremod; } export async function start(name: keyof typeof coremods): Promise { @@ -59,6 +60,7 @@ export async function startAll(): Promise { coremods.watcher = await import("../coremods/watcher"); coremods.commands = await import("../coremods/commands"); coremods.welcome = await import("../coremods/welcome"); + coremods.transparency = await import("../coremods/transparency"); await Promise.all( Object.entries(coremods).map(async ([name, mod]) => { From 91db00f83752d226af38cfc8ad93354ebc80e1c1 Mon Sep 17 00:00:00 2001 From: East_Arctica Date: Wed, 21 Feb 2024 16:49:30 -0500 Subject: [PATCH 18/47] Platform specific transparency in BrowserWindow --- src/main/index.ts | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/src/main/index.ts b/src/main/index.ts index 604b24e49..9a35ef7c3 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -77,16 +77,18 @@ class BrowserWindow extends electron.BrowserWindow { opts.webPreferences!.preload = join(__dirname, "./preload.js"); if (settings.get("transparentWindow")) { - opts.show = false; - // Menu bar needs to be remade -_- - opts.autoHideMenuBar = true; - // opts.frame = process.platform === "win32" ? false : opts.frame; - // TODO: Figure out what background color each OS needs. - opts.backgroundColor = "#0000000"; - - if (process.platform === "linux") { - opts.transparent = true; + switch (process.platform) { + case "win32": + // TODO: Menu bar will need to be remade + opts.autoHideMenuBar = true; + opts.show = false; // TODO: Unsure if this is needed everywhere + break; + case "linux": + opts.transparent = true; + break; } + // TODO: Determine what `frame` value is needed on each platform + // TODO: Determine what `backgroundColor` is needed on each platform } break; } @@ -104,10 +106,11 @@ class BrowserWindow extends electron.BrowserWindow { if (currentWindow === DiscordWindowType.DISCORD_CLIENT && settings.get("transparentWindow")) { this.on("ready-to-show", () => { - if (process.platform === "win32") { - vibe.applyEffect(this, "unified-acrylic"); - vibe.forceTheme(this, "dark"); - } + // if (process.platform === "win32") { + // vibe.applyEffect(this, "unified-acrylic"); + // vibe.forceTheme(this, "dark"); + // } + // TODO: unsure if this is needed this.setBackgroundColor("#00000000"); }); } From 8520f45d3e92dcda61e29b56ba9f40c4bd78fa03 Mon Sep 17 00:00:00 2001 From: East_Arctica Date: Wed, 21 Feb 2024 16:52:52 -0500 Subject: [PATCH 19/47] Unity todo messages and general cleaning --- src/globals.d.ts | 2 +- src/main/index.ts | 10 +++++----- src/main/ipc/transparency.ts | 4 ++-- src/preload.ts | 4 ++-- src/renderer/coremods/badges/index.tsx | 2 +- src/renderer/coremods/experiments/plaintextPatches.ts | 2 +- src/renderer/coremods/transparency/index.ts | 9 +++------ src/renderer/modules/components/Clickable.tsx | 2 +- src/renderer/modules/components/Text.tsx | 2 +- 9 files changed, 17 insertions(+), 20 deletions(-) diff --git a/src/globals.d.ts b/src/globals.d.ts index 45d8f7101..2a112b02a 100644 --- a/src/globals.d.ts +++ b/src/globals.d.ts @@ -1,6 +1,6 @@ /// -// TODO: Scope global types to each component +// @todo: Scope global types to each component import type { WebpackChunkGlobal } from "./types/discord"; import * as replugged from "./renderer/replugged"; diff --git a/src/main/index.ts b/src/main/index.ts index 9a35ef7c3..b4128c473 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -79,16 +79,16 @@ class BrowserWindow extends electron.BrowserWindow { if (settings.get("transparentWindow")) { switch (process.platform) { case "win32": - // TODO: Menu bar will need to be remade + // @todo: Menu bar will need to be remade opts.autoHideMenuBar = true; - opts.show = false; // TODO: Unsure if this is needed everywhere + opts.show = false; // @todo: Unsure if this is needed everywhere break; case "linux": opts.transparent = true; break; } - // TODO: Determine what `frame` value is needed on each platform - // TODO: Determine what `backgroundColor` is needed on each platform + // @todo: Determine what `frame` value is needed on each platform + // @todo: Determine what `backgroundColor` is needed on each platform } break; } @@ -110,7 +110,7 @@ class BrowserWindow extends electron.BrowserWindow { // vibe.applyEffect(this, "unified-acrylic"); // vibe.forceTheme(this, "dark"); // } - // TODO: unsure if this is needed + // @todo: unsure if this is needed this.setBackgroundColor("#00000000"); }); } diff --git a/src/main/ipc/transparency.ts b/src/main/ipc/transparency.ts index 103e9a6ff..68099d53f 100644 --- a/src/main/ipc/transparency.ts +++ b/src/main/ipc/transparency.ts @@ -30,7 +30,7 @@ ipcMain.handle( let windows = BrowserWindow.getAllWindows(); console.log(windows); - // TODO: Is it a bad idea to apply this to all active windows? + // @todo: Is it a bad idea to apply this to all active windows? windows.forEach((window) => vibe.applyEffect(window, effect)); currentEffect = effect; }, @@ -47,7 +47,7 @@ ipcMain.handle( (_, vibrancy: Parameters[0]) => { let windows = BrowserWindow.getAllWindows(); - // TODO: Is it a bad idea to apply this to all active windows? + // @todo: Is it a bad idea to apply this to all active windows? windows.forEach((window) => window.setVibrancy(vibrancy)); currentVibrancy = vibrancy; }, diff --git a/src/preload.ts b/src/preload.ts index 32a5eb3fe..e2df5240c 100644 --- a/src/preload.ts +++ b/src/preload.ts @@ -1,9 +1,9 @@ import { + BrowserWindow, type BrowserWindowConstructorOptions, contextBridge, ipcRenderer, webFrame, - BrowserWindow, } from "electron"; import { RepluggedIpcChannels } from "./types"; @@ -129,7 +129,7 @@ const RepluggedNative = { // eslint-disable-next-line @typescript-eslint/no-unused-vars openBrowserWindow: (opts: BrowserWindowConstructorOptions) => {}, // later - // @todo We probably want to move these somewhere else, but I'm putting them here for now because I'm too lazy to set anything else up + // @todo: We probably want to move these somewhere else, but I'm putting them here for now because I'm too lazy to set anything else up }; export type RepluggedNativeType = typeof RepluggedNative; diff --git a/src/renderer/coremods/badges/index.tsx b/src/renderer/coremods/badges/index.tsx index d7e935b26..6bc1b5657 100644 --- a/src/renderer/coremods/badges/index.tsx +++ b/src/renderer/coremods/badges/index.tsx @@ -60,7 +60,7 @@ export async function start(): Promise { if (!cache.has(id) || cache.get(id)!.lastFetch < Date.now() - REFRESH_INTERVAL) { cache.set( id, - // TODO: new backend + // @todo: new backend await fetch(`${generalSettings.get("apiUrl")}/api/v1/users/${id}`) .then(async (res) => { const body = (await res.json()) as Record & { diff --git a/src/renderer/coremods/experiments/plaintextPatches.ts b/src/renderer/coremods/experiments/plaintextPatches.ts index 6d18e8c6e..c3b08ada1 100644 --- a/src/renderer/coremods/experiments/plaintextPatches.ts +++ b/src/renderer/coremods/experiments/plaintextPatches.ts @@ -1,7 +1,7 @@ import { init } from "src/renderer/apis/settings"; import { type GeneralSettings, type PlaintextPatch, defaultSettings } from "src/types"; -// TODO: see if we can import this from General.tsx +// @todo: see if we can import this from General.tsx const generalSettings = await init( "dev.replugged.Settings", defaultSettings, diff --git a/src/renderer/coremods/transparency/index.ts b/src/renderer/coremods/transparency/index.ts index 761ae8a8f..1786a2458 100644 --- a/src/renderer/coremods/transparency/index.ts +++ b/src/renderer/coremods/transparency/index.ts @@ -1,11 +1,8 @@ -import { Logger } from "@replugged"; - -const logger = Logger.coremod("Transparency"); let observer: MutationObserver; export function start(): void { let html = document.body.parentElement!; - // RepluggedNative.transparency.applyEffect(); + observer = new MutationObserver(async (mutations) => { let cssModified = false; for (const mutation of mutations) { @@ -35,7 +32,7 @@ export function start(): void { return; } - // @ts-expect-error TODO: Check if the vibrancy is valid? + // @ts-expect-error @todo: Check if the vibrancy is valid? await RepluggedNative.transparency.applyEffect(transparencyEffect); break; } @@ -45,7 +42,7 @@ export function start(): void { return; } - // @ts-expect-error TODO: Check if the vibrancy is valid? + // @ts-expect-error @todo: Check if the vibrancy is valid? await RepluggedNative.transparency.setVibrancy(vibrancy); break; } diff --git a/src/renderer/modules/components/Clickable.tsx b/src/renderer/modules/components/Clickable.tsx index f840448ec..b10c8fd0d 100644 --- a/src/renderer/modules/components/Clickable.tsx +++ b/src/renderer/modules/components/Clickable.tsx @@ -1,7 +1,7 @@ import type React from "react"; import components from "../common/components"; -// TODO: generic type for tags? +// @todo: generic type for tags? type ClickableProps = React.ComponentPropsWithoutRef<"div"> & { tag?: keyof JSX.IntrinsicElements; ignoreKeyPress?: boolean; diff --git a/src/renderer/modules/components/Text.tsx b/src/renderer/modules/components/Text.tsx index c9f37167c..67179dbdf 100644 --- a/src/renderer/modules/components/Text.tsx +++ b/src/renderer/modules/components/Text.tsx @@ -64,7 +64,7 @@ export type Variant = | "display-lg" | "code"; -// TODO: generic type for tags? +// @todo: generic type for tags? interface TextProps extends React.ComponentPropsWithoutRef<"div"> { variant?: Variant; tag?: keyof JSX.IntrinsicElements; From 0f121a502b35fd621afbe25d1e31728f8816eaa4 Mon Sep 17 00:00:00 2001 From: East_Arctica Date: Wed, 21 Feb 2024 17:09:06 -0500 Subject: [PATCH 20/47] Fix Transparency IPC --- src/main/ipc/transparency.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/main/ipc/transparency.ts b/src/main/ipc/transparency.ts index 68099d53f..b8f2505d9 100644 --- a/src/main/ipc/transparency.ts +++ b/src/main/ipc/transparency.ts @@ -12,7 +12,7 @@ let currentEffect: Parameters[1] | null = null; ipcMain.handle( RepluggedIpcChannels.GET_TRANSPARENCY_EFFECT, (): Parameters[1] | null => { - if (DiscordNative.process.platform !== "win32") { + if (process.platform !== "win32") { console.warn("GET_TRANSPARENCY_EFFECT only works on Windows"); } @@ -23,14 +23,12 @@ ipcMain.handle( ipcMain.handle( RepluggedIpcChannels.APPLY_TRANSPARENCY_EFFECT, (_, effect: Parameters[1]) => { - if (DiscordNative.process.platform !== "win32") { + if (process.platform !== "win32") { console.warn("RepluggedNative.transparency.applyEffect only works on Windows"); return; } let windows = BrowserWindow.getAllWindows(); - console.log(windows); - // @todo: Is it a bad idea to apply this to all active windows? windows.forEach((window) => vibe.applyEffect(window, effect)); currentEffect = effect; }, @@ -47,7 +45,6 @@ ipcMain.handle( (_, vibrancy: Parameters[0]) => { let windows = BrowserWindow.getAllWindows(); - // @todo: Is it a bad idea to apply this to all active windows? windows.forEach((window) => window.setVibrancy(vibrancy)); currentVibrancy = vibrancy; }, From b1658b34e766374e2fd3292b6c328737e83c99a7 Mon Sep 17 00:00:00 2001 From: East_Arctica Date: Wed, 21 Feb 2024 17:15:17 -0500 Subject: [PATCH 21/47] Fix comment issues --- src/main/ipc/transparency.ts | 2 +- src/renderer/coremods/transparency/index.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/ipc/transparency.ts b/src/main/ipc/transparency.ts index b8f2505d9..570d07e2e 100644 --- a/src/main/ipc/transparency.ts +++ b/src/main/ipc/transparency.ts @@ -24,7 +24,7 @@ ipcMain.handle( RepluggedIpcChannels.APPLY_TRANSPARENCY_EFFECT, (_, effect: Parameters[1]) => { if (process.platform !== "win32") { - console.warn("RepluggedNative.transparency.applyEffect only works on Windows"); + console.warn("APPLY_TRANSPARENCY_EFFECT only works on Windows"); return; } diff --git a/src/renderer/coremods/transparency/index.ts b/src/renderer/coremods/transparency/index.ts index 1786a2458..0edd2ad73 100644 --- a/src/renderer/coremods/transparency/index.ts +++ b/src/renderer/coremods/transparency/index.ts @@ -32,7 +32,7 @@ export function start(): void { return; } - // @ts-expect-error @todo: Check if the vibrancy is valid? + // @ts-expect-error @todo: Check if the transparency effect is valid? await RepluggedNative.transparency.applyEffect(transparencyEffect); break; } From 7f9e614b90f31df17197859caad62063957b8681 Mon Sep 17 00:00:00 2001 From: East_Arctica Date: Wed, 21 Feb 2024 22:00:50 -0500 Subject: [PATCH 22/47] Fix reading of transparency styles --- src/renderer/coremods/transparency/index.ts | 49 +++++++++++++-------- 1 file changed, 31 insertions(+), 18 deletions(-) diff --git a/src/renderer/coremods/transparency/index.ts b/src/renderer/coremods/transparency/index.ts index 0edd2ad73..543b2fdcb 100644 --- a/src/renderer/coremods/transparency/index.ts +++ b/src/renderer/coremods/transparency/index.ts @@ -1,5 +1,12 @@ let observer: MutationObserver; +function getRootStringProperty(property: string): string { + const computedStyle = getComputedStyle(document.body); + const value = computedStyle.getPropertyValue(property); + + return value.split('"')[1]; +} + export function start(): void { let html = document.body.parentElement!; @@ -25,28 +32,34 @@ export function start(): void { } if (cssModified) { - switch (DiscordNative.process.platform) { - case "win32": { - const transparencyEffect = getComputedStyle(html).getPropertyValue("--window-win-blur"); - if (transparencyEffect === (await RepluggedNative.transparency.getEffect())) { - return; - } + // Originally this used requestAnimationFrame but it took to long + // so instead we setTimeout and pray. The setTimeout could be + // shorter if we wanted, but it's hard to say if it would + // work as consistently. + setTimeout(async () => { + switch (DiscordNative.process.platform) { + case "win32": { + const transparencyEffect = getRootStringProperty("--window-win-blur"); + if (transparencyEffect === (await RepluggedNative.transparency.getEffect())) { + return; + } - // @ts-expect-error @todo: Check if the transparency effect is valid? - await RepluggedNative.transparency.applyEffect(transparencyEffect); - break; - } - case "darwin": { - const vibrancy = getComputedStyle(html).getPropertyValue("--window-vibrancy"); - if (vibrancy === (await RepluggedNative.transparency.getVibrancy())) { - return; + // @ts-expect-error @todo: Check if the transparency effect is valid? + await RepluggedNative.transparency.applyEffect(transparencyEffect); + break; } + case "darwin": { + const vibrancy = getRootStringProperty("--window-vibrancy"); + if (vibrancy === (await RepluggedNative.transparency.getVibrancy())) { + return; + } - // @ts-expect-error @todo: Check if the vibrancy is valid? - await RepluggedNative.transparency.setVibrancy(vibrancy); - break; + // @ts-expect-error @todo: Check if the vibrancy is valid? + await RepluggedNative.transparency.setVibrancy(vibrancy); + break; + } } - } + }, 100); } }); From a62f1b43b5d6ef18c8f93b5a9b95d53fbdf5c195 Mon Sep 17 00:00:00 2001 From: East_Arctica Date: Fri, 1 Mar 2024 15:24:52 -0500 Subject: [PATCH 23/47] Fix advanced settings crash --- src/renderer/coremods/settings/pages/General.tsx | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/renderer/coremods/settings/pages/General.tsx b/src/renderer/coremods/settings/pages/General.tsx index 8f2f123c1..3967fe652 100644 --- a/src/renderer/coremods/settings/pages/General.tsx +++ b/src/renderer/coremods/settings/pages/General.tsx @@ -190,12 +190,6 @@ export const General = (): React.ReactElement => { {Messages.REPLUGGED_SETTINGS_REACT_DEVTOOLS} - - {Messages.REPLUGGED_SETTINGS_TRANSPARENT} - - Date: Fri, 1 Mar 2024 15:25:39 -0500 Subject: [PATCH 24/47] Use built in electron blur if available. --- cspell.json | 5 +++-- src/main/ipc/transparency.ts | 37 +++++++++++++++++++++++++++++++----- 2 files changed, 35 insertions(+), 7 deletions(-) diff --git a/cspell.json b/cspell.json index 606ef70fd..8196a5de4 100644 --- a/cspell.json +++ b/cspell.json @@ -10,6 +10,7 @@ "Automod", "autosize", "backoff", + "blurbehind", "blurple", "Chunkdiscord", "codemirror", @@ -48,6 +49,7 @@ "popout", "postpublish", "Promisable", + "pyke", "quickcss", "ratelimited", "rauenzi", @@ -67,8 +69,7 @@ "Vendicated", "Webauthn", "weblate", - "XLARGE", - "pyke" + "XLARGE" ], "ignoreWords": [], "import": [], diff --git a/src/main/ipc/transparency.ts b/src/main/ipc/transparency.ts index 570d07e2e..bba97f4e9 100644 --- a/src/main/ipc/transparency.ts +++ b/src/main/ipc/transparency.ts @@ -1,11 +1,27 @@ import { BrowserWindow, ipcMain } from "electron"; import { RepluggedIpcChannels } from "../../types"; -// @ts-expect-error Type defs are obtained through @pyke/vibe -import vibePath from "../../vibe.node"; +import os from "os"; let vibe: typeof import("@pyke/vibe"); -if (process.platform === "win32") { - vibe = require(vibePath) as unknown as typeof import("@pyke/vibe"); +let release = os.release().split(".").map(Number); +let usesVibe = + // Windows + process.platform === "win32" && + // Before Electron 24 + Number(process.versions.electron.split(".")[0]) < 24 && + // Before Windows 11 22H2+ + release[0] <= 10 && // 11 doesn't exist yet but it could. + release[1] < 22621; + +if (usesVibe) { + // @ts-expect-error Type defs are obtained through @pyke/vibe + import("../../vibe.node") + .then((module) => { + vibe = module as unknown as typeof import("@pyke/vibe"); + }) + .catch((error) => { + console.error("Failed to load vibe.", error); + }); } let currentEffect: Parameters[1] | null = null; @@ -29,7 +45,18 @@ ipcMain.handle( } let windows = BrowserWindow.getAllWindows(); - windows.forEach((window) => vibe.applyEffect(window, effect)); + windows.forEach((window) => { + if (usesVibe) { + // The valid options for setBackgroundMaterial are "auto" | "none" | "mica" | "acrylic" | "tabbed". + // This goes against vibe which allows for "unified-acrylic" and "blurbehind". + // Also, vibe does not allow for "auto", "none" or "tabbed" + + // @ts-expect-error Only exists in electron 24+, our types don't have this. + window.setBackgroundMaterial(effect === NULL ? "none" : effect); // NULL is used to disable. + } else { + vibe.applyEffect(window, effect); + } + }); currentEffect = effect; }, ); From a5451bf553fbeb264607fdb0702d4a5d040830db Mon Sep 17 00:00:00 2001 From: EastArctica Date: Sun, 10 Mar 2024 15:34:52 -0400 Subject: [PATCH 25/47] Fix applyEffect --- src/main/ipc/transparency.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/ipc/transparency.ts b/src/main/ipc/transparency.ts index bba97f4e9..ac06dce9d 100644 --- a/src/main/ipc/transparency.ts +++ b/src/main/ipc/transparency.ts @@ -38,7 +38,7 @@ ipcMain.handle( ipcMain.handle( RepluggedIpcChannels.APPLY_TRANSPARENCY_EFFECT, - (_, effect: Parameters[1]) => { + (_, effect: Parameters[1] | null) => { if (process.platform !== "win32") { console.warn("APPLY_TRANSPARENCY_EFFECT only works on Windows"); return; @@ -52,7 +52,9 @@ ipcMain.handle( // Also, vibe does not allow for "auto", "none" or "tabbed" // @ts-expect-error Only exists in electron 24+, our types don't have this. - window.setBackgroundMaterial(effect === NULL ? "none" : effect); // NULL is used to disable. + window.setBackgroundMaterial(effect === null ? "none" : effect); // NULL is used to disable. + } else if (effect === null) { + vibe.clearEffects(window); } else { vibe.applyEffect(window, effect); } From 9d0e1bdc7b3c0fa456f90318c75a07b26881b818 Mon Sep 17 00:00:00 2001 From: EastArctica Date: Sun, 10 Mar 2024 15:46:42 -0400 Subject: [PATCH 26/47] Fix vibe import --- src/main/ipc/transparency.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/ipc/transparency.ts b/src/main/ipc/transparency.ts index ac06dce9d..bc5123369 100644 --- a/src/main/ipc/transparency.ts +++ b/src/main/ipc/transparency.ts @@ -1,6 +1,8 @@ import { BrowserWindow, ipcMain } from "electron"; import { RepluggedIpcChannels } from "../../types"; import os from "os"; +// @ts-expect-error Type defs are obtained through @pyke/vibe +import vibePath from "../../vibe.node"; let vibe: typeof import("@pyke/vibe"); let release = os.release().split(".").map(Number); @@ -14,8 +16,7 @@ let usesVibe = release[1] < 22621; if (usesVibe) { - // @ts-expect-error Type defs are obtained through @pyke/vibe - import("../../vibe.node") + import(vibePath) .then((module) => { vibe = module as unknown as typeof import("@pyke/vibe"); }) From c047b443a4ee9e5a6ab239cec5c62c608d197eec Mon Sep 17 00:00:00 2001 From: EastArctica Date: Sun, 10 Mar 2024 15:52:42 -0400 Subject: [PATCH 27/47] Move from import to require --- src/main/ipc/transparency.ts | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/main/ipc/transparency.ts b/src/main/ipc/transparency.ts index bc5123369..02e861506 100644 --- a/src/main/ipc/transparency.ts +++ b/src/main/ipc/transparency.ts @@ -16,13 +16,11 @@ let usesVibe = release[1] < 22621; if (usesVibe) { - import(vibePath) - .then((module) => { - vibe = module as unknown as typeof import("@pyke/vibe"); - }) - .catch((error) => { - console.error("Failed to load vibe.", error); - }); + try { + vibe = require(vibePath) as unknown as typeof import("@pyke/vibe"); + } catch (error) { + console.error("Failed to load vibe.", error); + } } let currentEffect: Parameters[1] | null = null; From aa5d8e5485f5d3b1e1c5144ac9b661aaacc921f5 Mon Sep 17 00:00:00 2001 From: EastArctica Date: Sun, 10 Mar 2024 15:57:25 -0400 Subject: [PATCH 28/47] Fix inverted if --- src/main/ipc/transparency.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/ipc/transparency.ts b/src/main/ipc/transparency.ts index 02e861506..9e6567dba 100644 --- a/src/main/ipc/transparency.ts +++ b/src/main/ipc/transparency.ts @@ -45,7 +45,7 @@ ipcMain.handle( let windows = BrowserWindow.getAllWindows(); windows.forEach((window) => { - if (usesVibe) { + if (!usesVibe) { // The valid options for setBackgroundMaterial are "auto" | "none" | "mica" | "acrylic" | "tabbed". // This goes against vibe which allows for "unified-acrylic" and "blurbehind". // Also, vibe does not allow for "auto", "none" or "tabbed" From 04c3c4a2ada87ddd27b378dd304fb9d7c671028f Mon Sep 17 00:00:00 2001 From: East_Arctica Date: Tue, 14 Jan 2025 22:46:41 -0500 Subject: [PATCH 29/47] Remove vibe and implement setBackgroundMaterial --- package.json | 5 +- pnpm-lock.yaml | 8 +- scripts/build.mts | 3 - src/main/index.ts | 58 ++++++++----- src/main/ipc/index.ts | 9 +- src/main/ipc/transparency.ts | 80 ++++++++---------- src/preload.ts | 16 ++-- .../coremods/settings/pages/General.tsx | 8 +- src/renderer/coremods/transparency/index.ts | 31 +++++-- src/types/index.ts | 6 +- src/vibe.node | Bin 188928 -> 0 bytes 11 files changed, 118 insertions(+), 106 deletions(-) delete mode 100644 src/vibe.node diff --git a/package.json b/package.json index eaaee1f63..fa17d4667 100644 --- a/package.json +++ b/package.json @@ -97,10 +97,7 @@ "yargs": "^17.7.2", "zod": "^3.23.8" }, - "optionalDependencies": { - "@pyke/vibe": "^0.4.0" - }, "bin": { "replugged": "bin.mjs" } -} +} \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 540a51753..a5aa3194d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1019,8 +1019,8 @@ packages: '@types/methods@1.1.4': resolution: {integrity: sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ==} - '@types/node@18.19.64': - resolution: {integrity: sha512-955mDqvO2vFf/oL7V3WiUtiz+BugyX8uVbaT2H8oj3+8dRyH2FLiNdowe7eNqRM7IOIZvzDH76EoAT+gwm6aIQ==} + '@types/node@18.19.70': + resolution: {integrity: sha512-RE+K0+KZoEpDUbGGctnGdkrLFwi1eYKTlIHNl2Um98mUkGsm1u2Ff6Ltd0e8DktTtC98uy7rSj+hO8t/QuLoVQ==} '@types/node@20.17.6': resolution: {integrity: sha512-VEI7OdvK2wP7XHnsuXbAJnEpEkF6NjSN45QJlL4VGqZSXsnicpesdTWsg9RISeSdYd3yeRj/y3k5KGjUXYnFwQ==} @@ -3904,7 +3904,7 @@ snapshots: '@types/methods@1.1.4': {} - '@types/node@18.19.64': + '@types/node@18.19.70': dependencies: undici-types: 5.26.5 @@ -5815,7 +5815,7 @@ snapshots: standalone-electron-types@1.0.0: dependencies: - '@types/node': 18.19.64 + '@types/node': 18.19.70 string-width@1.0.2: dependencies: diff --git a/scripts/build.mts b/scripts/build.mts index 5f2fd90e0..5e54d370e 100644 --- a/scripts/build.mts +++ b/scripts/build.mts @@ -51,9 +51,6 @@ const contexts = await Promise.all([ target: `node${NODE_VERSION}`, outfile: `${distDir}/main.js`, external: ["electron", "original-fs"], - loader: { - ".node": "file", - }, }), // Preload esbuild.context({ diff --git a/src/main/index.ts b/src/main/index.ts index 6f234c404..16b63a291 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -3,14 +3,6 @@ import electron from "electron"; import { CONFIG_PATHS, readSettingsSync } from "src/util.mjs"; import type { RepluggedWebContents } from "../types"; import { getSetting } from "./ipc/settings"; -// @ts-expect-error Type defs are obtained through @pyke/vibe -import vibePath from "../vibe.node"; - -let vibe: typeof import("@pyke/vibe"); -if (process.platform === "win32") { - vibe = require(vibePath) as unknown as typeof import("@pyke/vibe"); - vibe.setup(electron.app); -} const settings = readSettingsSync("dev.replugged.Settings"); const electronPath = require.resolve("electron"); @@ -57,7 +49,8 @@ function windowTypeFromOpts(opts: InternalBrowserWindowConstructorOptions): Disc if (opts.webPreferences.nativeWindowOpen) { return DiscordWindowType.DISCORD_CLIENT; } else { - return DiscordWindowType.SPLASH_SCREEN; + // Splash Screen on macOS (Host 0.0.262+) & Windows (Host 0.0.293 / 1.0.17+) + return DiscordWindowType.DISCORD_CLIENT; } } @@ -80,16 +73,13 @@ class BrowserWindow extends electron.BrowserWindow { if (settings.get("transparentWindow")) { switch (process.platform) { case "win32": - // @todo: Menu bar will need to be remade - opts.autoHideMenuBar = true; - opts.show = false; // @todo: Unsure if this is needed everywhere + opts.transparent = true; + opts.backgroundColor = "#00000000"; break; case "linux": opts.transparent = true; break; } - // @todo: Determine what `frame` value is needed on each platform - // @todo: Determine what `backgroundColor` is needed on each platform } break; } @@ -105,19 +95,41 @@ class BrowserWindow extends electron.BrowserWindow { super(opts); - if (currentWindow === DiscordWindowType.DISCORD_CLIENT && settings.get("transparentWindow")) { - this.on("ready-to-show", () => { - // if (process.platform === "win32") { - // vibe.applyEffect(this, "unified-acrylic"); - // vibe.forceTheme(this, "dark"); - // } - // @todo: unsure if this is needed - this.setBackgroundColor("#00000000"); - }); + // Center the unmaximized location + if (settings.get("transparentWindow")) { + const currentDisplay = electron.screen.getDisplayNearestPoint(electron.screen.getCursorScreenPoint()) + this.repluggedPreviousBounds.x = currentDisplay.workArea.width / 2 - this.repluggedPreviousBounds.width / 2; + this.repluggedPreviousBounds.y = currentDisplay.workArea.height / 2 - this.repluggedPreviousBounds.height / 2; + this.maximize = this.repluggedToggleMaximize; + this.unmaximize = this.repluggedToggleMaximize; } (this.webContents as RepluggedWebContents).originalPreload = originalPreload; } + + + private repluggedPreviousBounds: Electron.Rectangle = { + width: 1400, + height: 900, + x: 0, + y: 0 + }; + + public repluggedToggleMaximize(): void { + // Determine whether the display is actually maximized already + let currentBounds = this.getBounds(); + const currentDisplay = electron.screen.getDisplayNearestPoint(electron.screen.getCursorScreenPoint()); + const workAreaSize = currentDisplay.workArea; + if (currentBounds.width === workAreaSize.width && currentBounds.height === workAreaSize.height) { + // Un-maximize + this.setBounds(this.repluggedPreviousBounds) + return; + } + + + this.repluggedPreviousBounds = this.getBounds() + this.setBounds({ x: workAreaSize.x + 1, y: workAreaSize.y + 1, width: workAreaSize.width, height: workAreaSize.height }) + } } Object.defineProperty(BrowserWindow, "name", { diff --git a/src/main/ipc/index.ts b/src/main/ipc/index.ts index f33dfb63e..102c9e8d9 100644 --- a/src/main/ipc/index.ts +++ b/src/main/ipc/index.ts @@ -1,19 +1,12 @@ import { ipcMain } from "electron"; import { RepluggedIpcChannels, type RepluggedWebContents } from "../../types"; -import "./plugins"; -import "./themes"; -import "./quick-css"; -import "./settings"; -import "./installer"; -import "./i18n"; -import "./react-devtools"; -import "./transparency"; import "./installer"; import "./plugins"; import "./quick-css"; import "./react-devtools"; import "./settings"; import "./themes"; +import './transparency'; ipcMain.on(RepluggedIpcChannels.GET_DISCORD_PRELOAD, (event) => { event.returnValue = (event.sender as RepluggedWebContents).originalPreload; diff --git a/src/main/ipc/transparency.ts b/src/main/ipc/transparency.ts index 9e6567dba..01ba4ea3d 100644 --- a/src/main/ipc/transparency.ts +++ b/src/main/ipc/transparency.ts @@ -1,64 +1,32 @@ import { BrowserWindow, ipcMain } from "electron"; import { RepluggedIpcChannels } from "../../types"; -import os from "os"; -// @ts-expect-error Type defs are obtained through @pyke/vibe -import vibePath from "../../vibe.node"; -let vibe: typeof import("@pyke/vibe"); -let release = os.release().split(".").map(Number); -let usesVibe = - // Windows - process.platform === "win32" && - // Before Electron 24 - Number(process.versions.electron.split(".")[0]) < 24 && - // Before Windows 11 22H2+ - release[0] <= 10 && // 11 doesn't exist yet but it could. - release[1] < 22621; - -if (usesVibe) { - try { - vibe = require(vibePath) as unknown as typeof import("@pyke/vibe"); - } catch (error) { - console.error("Failed to load vibe.", error); - } -} - -let currentEffect: Parameters[1] | null = null; +let backgroundMaterial: "auto" | "none" | "mica" | "acrylic" | "tabbed" | null = null; ipcMain.handle( - RepluggedIpcChannels.GET_TRANSPARENCY_EFFECT, - (): Parameters[1] | null => { + RepluggedIpcChannels.GET_BACKGROUND_MATERIAL, + (): "auto" | "none" | "mica" | "acrylic" | "tabbed" | null => { if (process.platform !== "win32") { - console.warn("GET_TRANSPARENCY_EFFECT only works on Windows"); + console.warn("GET_BACKGROUND_MATERIAL only works on Windows"); } - return currentEffect; + return backgroundMaterial; }, ); ipcMain.handle( - RepluggedIpcChannels.APPLY_TRANSPARENCY_EFFECT, - (_, effect: Parameters[1] | null) => { + RepluggedIpcChannels.SET_BACKGROUND_MATERIAL, + (_, material: "auto" | "none" | "mica" | "acrylic" | "tabbed" | null) => { if (process.platform !== "win32") { - console.warn("APPLY_TRANSPARENCY_EFFECT only works on Windows"); + console.warn("SET_BACKGROUND_MATERIAL only works on Windows"); return; } let windows = BrowserWindow.getAllWindows(); windows.forEach((window) => { - if (!usesVibe) { - // The valid options for setBackgroundMaterial are "auto" | "none" | "mica" | "acrylic" | "tabbed". - // This goes against vibe which allows for "unified-acrylic" and "blurbehind". - // Also, vibe does not allow for "auto", "none" or "tabbed" - - // @ts-expect-error Only exists in electron 24+, our types don't have this. - window.setBackgroundMaterial(effect === null ? "none" : effect); // NULL is used to disable. - } else if (effect === null) { - vibe.clearEffects(window); - } else { - vibe.applyEffect(window, effect); - } + // @ts-expect-error standalone-electron-types is not updated to have this. + window.setBackgroundMaterial(typeof material === "string" ? material : "none"); }); - currentEffect = effect; + backgroundMaterial = material; }, ); @@ -77,3 +45,29 @@ ipcMain.handle( currentVibrancy = vibrancy; }, ); + +let currentBackgroundColor = "#00000000"; +ipcMain.handle( + RepluggedIpcChannels.GET_BACKGROUND_COLOR, + (): string => { + if (process.platform !== "win32") { + console.warn("SET_BACKGROUND_COLOR only works on Windows"); + } + + return currentBackgroundColor; + } +) + +ipcMain.handle( + RepluggedIpcChannels.SET_BACKGROUND_COLOR, + (_, color: string | undefined) => { + if (process.platform !== "win32") { + console.warn("SET_BACKGROUND_COLOR only works on Windows"); + return; + } + + let windows = BrowserWindow.getAllWindows(); + windows.forEach((window) => window.setBackgroundColor(color || "#00000000")); + currentBackgroundColor = color || "#00000000"; + }, +); diff --git a/src/preload.ts b/src/preload.ts index a7560ec52..37dc5778a 100644 --- a/src/preload.ts +++ b/src/preload.ts @@ -17,8 +17,6 @@ import type { RepluggedPlugin, RepluggedTheme, } from "./types"; -// Note that this may ONLY be used for types. -import vibe from "@pyke/vibe"; let version = ""; void ipcRenderer.invoke(RepluggedIpcChannels.GET_REPLUGGED_VERSION).then((v) => { @@ -106,10 +104,14 @@ const RepluggedNative = { }, transparency: { - getEffect: (): Promise[1]> => - ipcRenderer.invoke(RepluggedIpcChannels.GET_TRANSPARENCY_EFFECT), - applyEffect: (effect: Parameters[1]): Promise => - ipcRenderer.invoke(RepluggedIpcChannels.APPLY_TRANSPARENCY_EFFECT, effect), + getBackgroundMaterial: (): Promise<"auto" | "none" | "mica" | "acrylic" | "tabbed"> => + ipcRenderer.invoke(RepluggedIpcChannels.GET_BACKGROUND_MATERIAL), + setBackgroundMaterial: (effect: "auto" | "none" | "mica" | "acrylic" | "tabbed"): Promise => + ipcRenderer.invoke(RepluggedIpcChannels.SET_BACKGROUND_MATERIAL, effect), + getBackgroundColor: (): Promise => + ipcRenderer.invoke(RepluggedIpcChannels.GET_BACKGROUND_COLOR), + setBackgroundColor: (color: string): Promise => + ipcRenderer.invoke(RepluggedIpcChannels.SET_BACKGROUND_COLOR, color), getVibrancy: (): Promise[0]> => ipcRenderer.invoke(RepluggedIpcChannels.GET_VIBRANCY), setVibrancy: ( @@ -121,7 +123,7 @@ const RepluggedNative = { getVersion: () => version, // eslint-disable-next-line @typescript-eslint/no-unused-vars - openBrowserWindow: (opts: BrowserWindowConstructorOptions) => {}, // later + openBrowserWindow: (opts: BrowserWindowConstructorOptions) => { }, // later // @todo: We probably want to move these somewhere else, but I'm putting them here for now because I'm too lazy to set anything else up }; diff --git a/src/renderer/coremods/settings/pages/General.tsx b/src/renderer/coremods/settings/pages/General.tsx index d07f0aa48..e687191f8 100644 --- a/src/renderer/coremods/settings/pages/General.tsx +++ b/src/renderer/coremods/settings/pages/General.tsx @@ -120,8 +120,8 @@ export const General = (): React.ReactElement => { DiscordNative.process.platform === "win32") && ( {DiscordNative.process.platform === "linux" - ? Messages.REPLUGGED_SETTINGS_TRANSPARENT_ISSUES_LINUX.format() - : Messages.REPLUGGED_SETTINGS_TRANSPARENT_ISSUES_WINDOWS.format()} + ? intl.format(t.REPLUGGED_SETTINGS_TRANSPARENT_ISSUES_LINUX, {}) + : intl.format(t.REPLUGGED_SETTINGS_TRANSPARENT_ISSUES_WINDOWS, {})} )} @@ -131,8 +131,8 @@ export const General = (): React.ReactElement => { transOnChange(value); restartModal(true); }} - note={Messages.REPLUGGED_SETTINGS_TRANSPARENT_DESC.format()}> - {Messages.REPLUGGED_SETTINGS_TRANSPARENT} + note={intl.format(t.REPLUGGED_SETTINGS_TRANSPARENT_DESC, {})}> + {intl.string(t.REPLUGGED_SETTINGS_TRANSPARENT)} { switch (DiscordNative.process.platform) { case "win32": { - const transparencyEffect = getRootStringProperty("--window-win-blur"); - if (transparencyEffect === (await RepluggedNative.transparency.getEffect())) { - return; + const backgroundMaterial = getRootStringProperty("--window-background-material"); + if (backgroundMaterial !== (await RepluggedNative.transparency.getBackgroundMaterial())) { + logger.log('Setting background material to:', backgroundMaterial); + // @ts-expect-error @todo: Check if the transparency effect is valid? + await RepluggedNative.transparency.setBackgroundMaterial(backgroundMaterial); } - // @ts-expect-error @todo: Check if the transparency effect is valid? - await RepluggedNative.transparency.applyEffect(transparencyEffect); + const backgroundColor = getRootProperty("--window-background-color"); + if (backgroundColor !== (await RepluggedNative.transparency.getBackgroundColor())) { + logger.log('Setting background color to:', backgroundColor); + await RepluggedNative.transparency.setBackgroundColor(backgroundColor); + } break; } case "darwin": { const vibrancy = getRootStringProperty("--window-vibrancy"); if (vibrancy === (await RepluggedNative.transparency.getVibrancy())) { - return; + break; } + logger.log('Setting vibrancy effect to:', vibrancy); // @ts-expect-error @todo: Check if the vibrancy is valid? await RepluggedNative.transparency.setVibrancy(vibrancy); break; diff --git a/src/types/index.ts b/src/types/index.ts index 83eb351d4..e4bf46528 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -32,10 +32,12 @@ export enum RepluggedIpcChannels { OPEN_QUICKCSS_FOLDER = "REPLUGGED_OPEN_QUICKCSS_FOLDER", GET_REPLUGGED_VERSION = "REPLUGGED_GET_REPLUGGED_VERSION", DOWNLOAD_REACT_DEVTOOLS = "REPLUGGED_DOWNLOAD_REACT_DEVTOOLS", - GET_TRANSPARENCY_EFFECT = "REPLUGGED_GET_TRANSPARENCY_EFFECT", - APPLY_TRANSPARENCY_EFFECT = "REPLUGGED_APPLY_TRANSPARENCY_EFFECT", + GET_BACKGROUND_MATERIAL = "REPLUGGED_GET_BACKGROUND_MATERIAL", + SET_BACKGROUND_MATERIAL = "REPLUGGED_SET_BACKGROUND_MATERIAL", GET_VIBRANCY = "REPLUGGED_GET_VIBRANCY", SET_VIBRANCY = "REPLUGGED_SET_VIBRANCY", + GET_BACKGROUND_COLOR = "REPLUGGED_GET_BACKGROUND_COLOR", + SET_BACKGROUND_COLOR = "REPLUGGED_SET_BACKGROUND_COLOR", } export interface RepluggedAnnouncement { diff --git a/src/vibe.node b/src/vibe.node deleted file mode 100644 index dd6fbe432e55948d87dd742361755e6731b73ada..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 188928 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

|`4PY0E2snfjCu(=V;8!;2 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 Date: Tue, 14 Jan 2025 22:49:03 -0500 Subject: [PATCH 30/47] Remove old cspell definitions --- cspell.json | 2 -- 1 file changed, 2 deletions(-) diff --git a/cspell.json b/cspell.json index 16e1f64d1..f3441177c 100644 --- a/cspell.json +++ b/cspell.json @@ -8,7 +8,6 @@ "asar", "automod", "autosize", - "backoff", "blurbehind", "blurple", "channeltextarea", @@ -50,7 +49,6 @@ "outfile", "popout", "Promisable", - "pyke", "quickcss", "RATELIMITED", "rauenzi", From aa992d01adbdccca6add3e4bdd124c56a94904e2 Mon Sep 17 00:00:00 2001 From: East_Arctica Date: Tue, 14 Jan 2025 22:49:29 -0500 Subject: [PATCH 31/47] Remove cspell blurbehind definition --- cspell.json | 1 - 1 file changed, 1 deletion(-) diff --git a/cspell.json b/cspell.json index f3441177c..bfdff9467 100644 --- a/cspell.json +++ b/cspell.json @@ -8,7 +8,6 @@ "asar", "automod", "autosize", - "blurbehind", "blurple", "channeltextarea", "Chunkdiscord", From b2a80663566bd2673fac35e8e32d02841bf4dec2 Mon Sep 17 00:00:00 2001 From: East_Arctica Date: Tue, 14 Jan 2025 22:53:14 -0500 Subject: [PATCH 32/47] Add unmaximize cspell and prettier --- cspell.json | 2 ++ src/main/index.ts | 34 ++++++++++++------ src/main/ipc/index.ts | 2 +- src/main/ipc/transparency.ts | 36 ++++++++----------- src/preload.ts | 7 ++-- src/renderer/apis/settings.ts | 4 +-- src/renderer/coremods/commands/commands.ts | 8 ++--- src/renderer/coremods/commands/index.ts | 12 +++---- .../coremods/settings/pages/Updater.tsx | 5 ++- src/renderer/coremods/transparency/index.ts | 10 +++--- src/renderer/util.ts | 13 ++++--- 11 files changed, 71 insertions(+), 62 deletions(-) diff --git a/cspell.json b/cspell.json index bfdff9467..ac5cc5a59 100644 --- a/cspell.json +++ b/cspell.json @@ -64,6 +64,8 @@ "uninject", "uninjector", "uninjectors", + "unmaximize", + "unmaximized", "Vendicated", "webauthn", "weblate", diff --git a/src/main/index.ts b/src/main/index.ts index 16b63a291..26465752d 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -97,9 +97,13 @@ class BrowserWindow extends electron.BrowserWindow { // Center the unmaximized location if (settings.get("transparentWindow")) { - const currentDisplay = electron.screen.getDisplayNearestPoint(electron.screen.getCursorScreenPoint()) - this.repluggedPreviousBounds.x = currentDisplay.workArea.width / 2 - this.repluggedPreviousBounds.width / 2; - this.repluggedPreviousBounds.y = currentDisplay.workArea.height / 2 - this.repluggedPreviousBounds.height / 2; + const currentDisplay = electron.screen.getDisplayNearestPoint( + electron.screen.getCursorScreenPoint(), + ); + this.repluggedPreviousBounds.x = + currentDisplay.workArea.width / 2 - this.repluggedPreviousBounds.width / 2; + this.repluggedPreviousBounds.y = + currentDisplay.workArea.height / 2 - this.repluggedPreviousBounds.height / 2; this.maximize = this.repluggedToggleMaximize; this.unmaximize = this.repluggedToggleMaximize; } @@ -107,28 +111,36 @@ class BrowserWindow extends electron.BrowserWindow { (this.webContents as RepluggedWebContents).originalPreload = originalPreload; } - private repluggedPreviousBounds: Electron.Rectangle = { width: 1400, height: 900, x: 0, - y: 0 + y: 0, }; public repluggedToggleMaximize(): void { // Determine whether the display is actually maximized already let currentBounds = this.getBounds(); - const currentDisplay = electron.screen.getDisplayNearestPoint(electron.screen.getCursorScreenPoint()); + const currentDisplay = electron.screen.getDisplayNearestPoint( + electron.screen.getCursorScreenPoint(), + ); const workAreaSize = currentDisplay.workArea; - if (currentBounds.width === workAreaSize.width && currentBounds.height === workAreaSize.height) { + if ( + currentBounds.width === workAreaSize.width && + currentBounds.height === workAreaSize.height + ) { // Un-maximize - this.setBounds(this.repluggedPreviousBounds) + this.setBounds(this.repluggedPreviousBounds); return; } - - this.repluggedPreviousBounds = this.getBounds() - this.setBounds({ x: workAreaSize.x + 1, y: workAreaSize.y + 1, width: workAreaSize.width, height: workAreaSize.height }) + this.repluggedPreviousBounds = this.getBounds(); + this.setBounds({ + x: workAreaSize.x + 1, + y: workAreaSize.y + 1, + width: workAreaSize.width, + height: workAreaSize.height, + }); } } diff --git a/src/main/ipc/index.ts b/src/main/ipc/index.ts index 102c9e8d9..6878491b7 100644 --- a/src/main/ipc/index.ts +++ b/src/main/ipc/index.ts @@ -6,7 +6,7 @@ import "./quick-css"; import "./react-devtools"; import "./settings"; import "./themes"; -import './transparency'; +import "./transparency"; ipcMain.on(RepluggedIpcChannels.GET_DISCORD_PRELOAD, (event) => { event.returnValue = (event.sender as RepluggedWebContents).originalPreload; diff --git a/src/main/ipc/transparency.ts b/src/main/ipc/transparency.ts index 01ba4ea3d..d582c2cc0 100644 --- a/src/main/ipc/transparency.ts +++ b/src/main/ipc/transparency.ts @@ -47,27 +47,21 @@ ipcMain.handle( ); let currentBackgroundColor = "#00000000"; -ipcMain.handle( - RepluggedIpcChannels.GET_BACKGROUND_COLOR, - (): string => { - if (process.platform !== "win32") { - console.warn("SET_BACKGROUND_COLOR only works on Windows"); - } - - return currentBackgroundColor; +ipcMain.handle(RepluggedIpcChannels.GET_BACKGROUND_COLOR, (): string => { + if (process.platform !== "win32") { + console.warn("SET_BACKGROUND_COLOR only works on Windows"); } -) -ipcMain.handle( - RepluggedIpcChannels.SET_BACKGROUND_COLOR, - (_, color: string | undefined) => { - if (process.platform !== "win32") { - console.warn("SET_BACKGROUND_COLOR only works on Windows"); - return; - } + return currentBackgroundColor; +}); - let windows = BrowserWindow.getAllWindows(); - windows.forEach((window) => window.setBackgroundColor(color || "#00000000")); - currentBackgroundColor = color || "#00000000"; - }, -); +ipcMain.handle(RepluggedIpcChannels.SET_BACKGROUND_COLOR, (_, color: string | undefined) => { + if (process.platform !== "win32") { + console.warn("SET_BACKGROUND_COLOR only works on Windows"); + return; + } + + let windows = BrowserWindow.getAllWindows(); + windows.forEach((window) => window.setBackgroundColor(color || "#00000000")); + currentBackgroundColor = color || "#00000000"; +}); diff --git a/src/preload.ts b/src/preload.ts index 37dc5778a..42f4e2cbd 100644 --- a/src/preload.ts +++ b/src/preload.ts @@ -106,8 +106,9 @@ const RepluggedNative = { transparency: { getBackgroundMaterial: (): Promise<"auto" | "none" | "mica" | "acrylic" | "tabbed"> => ipcRenderer.invoke(RepluggedIpcChannels.GET_BACKGROUND_MATERIAL), - setBackgroundMaterial: (effect: "auto" | "none" | "mica" | "acrylic" | "tabbed"): Promise => - ipcRenderer.invoke(RepluggedIpcChannels.SET_BACKGROUND_MATERIAL, effect), + setBackgroundMaterial: ( + effect: "auto" | "none" | "mica" | "acrylic" | "tabbed", + ): Promise => ipcRenderer.invoke(RepluggedIpcChannels.SET_BACKGROUND_MATERIAL, effect), getBackgroundColor: (): Promise => ipcRenderer.invoke(RepluggedIpcChannels.GET_BACKGROUND_COLOR), setBackgroundColor: (color: string): Promise => @@ -123,7 +124,7 @@ const RepluggedNative = { getVersion: () => version, // eslint-disable-next-line @typescript-eslint/no-unused-vars - openBrowserWindow: (opts: BrowserWindowConstructorOptions) => { }, // later + openBrowserWindow: (opts: BrowserWindowConstructorOptions) => {}, // later // @todo: We probably want to move these somewhere else, but I'm putting them here for now because I'm too lazy to set anything else up }; diff --git a/src/renderer/apis/settings.ts b/src/renderer/apis/settings.ts index 2cdfd9a51..93708dd52 100644 --- a/src/renderer/apis/settings.ts +++ b/src/renderer/apis/settings.ts @@ -57,8 +57,8 @@ export class SettingsManager, D extends ke ): K extends D ? NonNullable : F extends null | undefined - ? T[K] | undefined - : NonNullable | F { + ? T[K] | undefined + : NonNullable | F { if (typeof this.#settings === "undefined") { throw new Error(`Settings not loaded for namespace ${this.namespace}`); } diff --git a/src/renderer/coremods/commands/commands.ts b/src/renderer/coremods/commands/commands.ts index af1c18bab..26a5f34c5 100644 --- a/src/renderer/coremods/commands/commands.ts +++ b/src/renderer/coremods/commands/commands.ts @@ -352,8 +352,8 @@ export function loadCommands(): void { listType === "enabled" ? enabledString : listType === "disabled" - ? disabledString - : `${enabledString}\n\n${disabledString}`; + ? disabledString + : `${enabledString}\n\n${disabledString}`; return { send, @@ -384,8 +384,8 @@ export function loadCommands(): void { listType === "enabled" ? enabledString : listType === "disabled" - ? disabledString - : `${enabledString}\n\n${disabledString}`; + ? disabledString + : `${enabledString}\n\n${disabledString}`; return { send, diff --git a/src/renderer/coremods/commands/index.ts b/src/renderer/coremods/commands/index.ts index 86d167a4d..ec168606b 100644 --- a/src/renderer/coremods/commands/index.ts +++ b/src/renderer/coremods/commands/index.ts @@ -136,9 +136,9 @@ async function injectApplicationCommandIndexStore(): Promise { } else { res.commands = Array.isArray(res.commands) ? [ - ...commandsToAdd, - ...res.commands.filter((command) => !commandsToAdd.includes(command)), - ] + ...commandsToAdd, + ...res.commands.filter((command) => !commandsToAdd.includes(command)), + ] : commandsToAdd; } } @@ -211,9 +211,9 @@ async function injectApplicationCommandIndexStore(): Promise { .flat(10); res.commands = Array.isArray(res.commands) ? [ - ...commandsToAdd, - ...res.commands.filter((command) => !commandsToAdd.includes(command)), - ] + ...commandsToAdd, + ...res.commands.filter((command) => !commandsToAdd.includes(command)), + ] : commandsToAdd; } diff --git a/src/renderer/coremods/settings/pages/Updater.tsx b/src/renderer/coremods/settings/pages/Updater.tsx index c6eae1ab1..a799f8250 100644 --- a/src/renderer/coremods/settings/pages/Updater.tsx +++ b/src/renderer/coremods/settings/pages/Updater.tsx @@ -24,9 +24,8 @@ const logger = Logger.coremod("Settings:Updater"); export const Updater = (): React.ReactElement => { const [checking, setChecking] = React.useState(false); - const [updatesAvailable, setUpdatesAvailable] = React.useState< - Array - >(getAvailableUpdates()); + const [updatesAvailable, setUpdatesAvailable] = + React.useState>(getAvailableUpdates()); const [updatePromises, setUpdatePromises] = React.useState>>({}); const [didInstallAll, setDidInstallAll] = React.useState(false); const [lastChecked, setLastChecked] = useSettingArray(updaterSettings, "lastChecked"); diff --git a/src/renderer/coremods/transparency/index.ts b/src/renderer/coremods/transparency/index.ts index af7b0912d..4c582877b 100644 --- a/src/renderer/coremods/transparency/index.ts +++ b/src/renderer/coremods/transparency/index.ts @@ -49,15 +49,17 @@ export function start(): void { switch (DiscordNative.process.platform) { case "win32": { const backgroundMaterial = getRootStringProperty("--window-background-material"); - if (backgroundMaterial !== (await RepluggedNative.transparency.getBackgroundMaterial())) { - logger.log('Setting background material to:', backgroundMaterial); + if ( + backgroundMaterial !== (await RepluggedNative.transparency.getBackgroundMaterial()) + ) { + logger.log("Setting background material to:", backgroundMaterial); // @ts-expect-error @todo: Check if the transparency effect is valid? await RepluggedNative.transparency.setBackgroundMaterial(backgroundMaterial); } const backgroundColor = getRootProperty("--window-background-color"); if (backgroundColor !== (await RepluggedNative.transparency.getBackgroundColor())) { - logger.log('Setting background color to:', backgroundColor); + logger.log("Setting background color to:", backgroundColor); await RepluggedNative.transparency.setBackgroundColor(backgroundColor); } break; @@ -68,7 +70,7 @@ export function start(): void { break; } - logger.log('Setting vibrancy effect to:', vibrancy); + logger.log("Setting vibrancy effect to:", vibrancy); // @ts-expect-error @todo: Check if the vibrancy is valid? await RepluggedNative.transparency.setVibrancy(vibrancy); break; diff --git a/src/renderer/util.ts b/src/renderer/util.ts index fadacfec5..5818500e4 100644 --- a/src/renderer/util.ts +++ b/src/renderer/util.ts @@ -197,8 +197,8 @@ export function useSetting< value: K extends D ? NonNullable : F extends null | undefined - ? T[K] | undefined - : NonNullable | F; + ? T[K] | undefined + : NonNullable | F; onChange: (newValue: ValType) => void; } { const initial = settings.get(key, fallback); @@ -237,8 +237,8 @@ export function useSettingArray< K extends D ? NonNullable : F extends null | undefined - ? T[K] | undefined - : NonNullable | F, + ? T[K] | undefined + : NonNullable | F, (newValue: ValType) => void, ] { const { value, onChange } = useSetting(settings, key, fallback); @@ -256,9 +256,8 @@ type UnionToIntersection = (U extends never ? never : (k: U) => void) extends type ObjectType = Record; -type ExtractObjectType = O extends Array - ? UnionToIntersection - : never; +type ExtractObjectType = + O extends Array ? UnionToIntersection : never; export function virtualMerge(...objects: O): ExtractObjectType { const fallback = {}; From ff063ca71c49f696ce591f9c96c2796a6bc46ed3 Mon Sep 17 00:00:00 2001 From: East_Arctica Date: Wed, 15 Jan 2025 20:37:51 -0500 Subject: [PATCH 33/47] "hook" (un)maximize more cleanly --- src/main/index.ts | 90 +++++++++++++++++++++++++---------------------- 1 file changed, 48 insertions(+), 42 deletions(-) diff --git a/src/main/index.ts b/src/main/index.ts index 26465752d..8f5242bbb 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -74,7 +74,6 @@ class BrowserWindow extends electron.BrowserWindow { switch (process.platform) { case "win32": opts.transparent = true; - opts.backgroundColor = "#00000000"; break; case "linux": opts.transparent = true; @@ -97,50 +96,57 @@ class BrowserWindow extends electron.BrowserWindow { // Center the unmaximized location if (settings.get("transparentWindow")) { - const currentDisplay = electron.screen.getDisplayNearestPoint( - electron.screen.getCursorScreenPoint(), - ); - this.repluggedPreviousBounds.x = - currentDisplay.workArea.width / 2 - this.repluggedPreviousBounds.width / 2; - this.repluggedPreviousBounds.y = - currentDisplay.workArea.height / 2 - this.repluggedPreviousBounds.height / 2; - this.maximize = this.repluggedToggleMaximize; - this.unmaximize = this.repluggedToggleMaximize; - } - - (this.webContents as RepluggedWebContents).originalPreload = originalPreload; - } - - private repluggedPreviousBounds: Electron.Rectangle = { - width: 1400, - height: 900, - x: 0, - y: 0, - }; + let lastBounds = this.getBounds(); + // Default to the center of the screen at 1440x810 scale for a 1080p monitor (75%) + let primaryDisplaySize = electron.screen.getPrimaryDisplay().workAreaSize; + let lastLastBounds = { + width: primaryDisplaySize.width * 0.75, + height: primaryDisplaySize.height * 0.75, + x: primaryDisplaySize.width / 2 - primaryDisplaySize.width * 0.75 / 2, + y: primaryDisplaySize.height / 2 - primaryDisplaySize.height * 0.75 / 2, + }; + let lastResize = Date.now(); + this.on('resize', () => { + const bounds = this.getBounds(); + lastLastBounds = lastBounds; + lastBounds = bounds; + lastResize = Date.now(); + }); + + this.on('maximize', () => { + // Get the display at the center of the window + const screenBounds = this.getBounds(); + const windowDisplay = electron.screen.getDisplayNearestPoint({ x: screenBounds.x + screenBounds.width / 2, y: screenBounds.y + screenBounds.height / 2 }); + const workAreaSize = windowDisplay.workArea; + + const isSizeMaximized = lastBounds.width === workAreaSize.width && lastBounds.height === workAreaSize.height; + const isPositionMaximized = (lastBounds.x === workAreaSize.x + 1 && lastBounds.y === workAreaSize.y + 1); + + // if we haven't resized in the last few ms, we probably didn't actually maximize and should instead unmaximize + if (lastResize < Date.now() - 10 || (isSizeMaximized && isPositionMaximized)) { + // Calculate new x, y to be in the center of the monitor + this.setBounds({ + x: workAreaSize.width / 2 - lastLastBounds.width / 2 + workAreaSize.x, + y: workAreaSize.height / 2 - lastLastBounds.height / 2 + workAreaSize.y, + width: lastLastBounds.width, + height: lastLastBounds.height, + }); + + lastResize = Date.now(); + return; + } - public repluggedToggleMaximize(): void { - // Determine whether the display is actually maximized already - let currentBounds = this.getBounds(); - const currentDisplay = electron.screen.getDisplayNearestPoint( - electron.screen.getCursorScreenPoint(), - ); - const workAreaSize = currentDisplay.workArea; - if ( - currentBounds.width === workAreaSize.width && - currentBounds.height === workAreaSize.height - ) { - // Un-maximize - this.setBounds(this.repluggedPreviousBounds); - return; + // Move the window to 1,1 to mitigate the window going grey when maximized + this.setBounds({ + x: workAreaSize.x + 1, + y: workAreaSize.y + 1, + width: screenBounds.width, + height: screenBounds.height, + }); + }); } - this.repluggedPreviousBounds = this.getBounds(); - this.setBounds({ - x: workAreaSize.x + 1, - y: workAreaSize.y + 1, - width: workAreaSize.width, - height: workAreaSize.height, - }); + (this.webContents as RepluggedWebContents).originalPreload = originalPreload; } } From 3551af4068a9a77d013820dd0f1ea21b997af111 Mon Sep 17 00:00:00 2001 From: East_Arctica Date: Thu, 16 Jan 2025 13:45:22 -0500 Subject: [PATCH 34/47] Add note about maximization --- src/main/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/index.ts b/src/main/index.ts index 8f5242bbb..4d26f57a3 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -137,6 +137,7 @@ class BrowserWindow extends electron.BrowserWindow { } // Move the window to 1,1 to mitigate the window going grey when maximized + // Note that the window doesn't seem to visually be at 1,1, but that's enough to prevent the greying this.setBounds({ x: workAreaSize.x + 1, y: workAreaSize.y + 1, From 36932d9ea4b07d666554ffa4b5bae3a8fb79f605 Mon Sep 17 00:00:00 2001 From: East_Arctica Date: Thu, 16 Jan 2025 13:48:24 -0500 Subject: [PATCH 35/47] Add ColorPicker component --- src/renderer/modules/common/components.ts | 6 ++++ .../modules/components/ColorPicker.ts | 32 +++++++++++++++++++ .../components/ColorPickerCustomButton.ts | 15 +++++++++ .../components/ColorPickerDefaultButton.ts | 15 +++++++++ 4 files changed, 68 insertions(+) create mode 100644 src/renderer/modules/components/ColorPicker.ts create mode 100644 src/renderer/modules/components/ColorPickerCustomButton.ts create mode 100644 src/renderer/modules/components/ColorPickerDefaultButton.ts diff --git a/src/renderer/modules/common/components.ts b/src/renderer/modules/common/components.ts index 8d3484076..455882013 100644 --- a/src/renderer/modules/common/components.ts +++ b/src/renderer/modules/common/components.ts @@ -19,6 +19,9 @@ import type { TextInputType } from "../components/TextInput"; import type { OriginalTooltipType } from "../components/Tooltip"; import { waitForProps } from "../webpack"; import type { CreateToast, ShowToast } from "./toast"; +import { ColorPickerType } from "@components/ColorPicker"; +import { ColorPickerCustomButtonType } from "@components/ColorPickerCustomButton"; +import { ColorPickerDefaultButtonType } from "@components/ColorPickerDefaultButton"; // Expand this as needed interface DiscordComponents { @@ -56,6 +59,9 @@ interface DiscordComponents { TextArea: TextAreaType; TextInput: TextInputType; Tooltip: OriginalTooltipType; + ColorPicker: ColorPickerType; + ColorPickerCustomButton: ColorPickerCustomButtonType; + ColorPickerDefaultButton: ColorPickerDefaultButtonType; } export default await waitForProps("FormText", "MenuItem"); diff --git a/src/renderer/modules/components/ColorPicker.ts b/src/renderer/modules/components/ColorPicker.ts new file mode 100644 index 000000000..3d298ab28 --- /dev/null +++ b/src/renderer/modules/components/ColorPicker.ts @@ -0,0 +1,32 @@ +import type React from "react"; +import components from "../common/components"; + +// From Tooltip.tsx +const Positions = { + TOP: "top", + BOTTOM: "bottom", + LEFT: "left", + RIGHT: "right", + CENTER: "center", + WINDOW_CENTER: "window_center", +} as const; + +interface ColorPickerProps { + className?: string, + defaultColor?: number, + customColor?: number, + colors: number[], + value?: number, + disabled?: boolean, + onChange?: (value: number, name: string) => void; + renderDefaultButton: (props: object) => React.ReactElement, + renderCustomButton: (props: object) => React.ReactElement, + colorContainerClassName?: string + customPickerPosition?: (typeof Positions)[keyof typeof Positions]; +} + +export type ColorPickerType = React.ComponentClass & { + defaultProps: ColorPickerProps; +}; + +export default components.ColorPicker; diff --git a/src/renderer/modules/components/ColorPickerCustomButton.ts b/src/renderer/modules/components/ColorPickerCustomButton.ts new file mode 100644 index 000000000..08feff634 --- /dev/null +++ b/src/renderer/modules/components/ColorPickerCustomButton.ts @@ -0,0 +1,15 @@ +import type React from "react"; +import components from "../common/components"; + +interface ColorPickerCustomButtonProps { + color?: number, + value?: number, + disabled?: boolean, + onChange?: (value: number, name: string) => void; +} + +export type ColorPickerCustomButtonType = React.ComponentClass & { + defaultProps: ColorPickerCustomButtonProps; +}; + +export default components.ColorPickerCustomButton; diff --git a/src/renderer/modules/components/ColorPickerDefaultButton.ts b/src/renderer/modules/components/ColorPickerDefaultButton.ts new file mode 100644 index 000000000..5d05807ab --- /dev/null +++ b/src/renderer/modules/components/ColorPickerDefaultButton.ts @@ -0,0 +1,15 @@ +import type React from "react"; +import components from "../common/components"; + +interface ColorPickerDefaultButtonProps { + customColor?: number, + value?: number, + disabled?: boolean, + "aria-label"?: string; +} + +export type ColorPickerDefaultButtonType = React.ComponentClass & { + defaultProps: ColorPickerDefaultButtonProps; +}; + +export default components.ColorPickerDefaultButton; From b0bd3a828f0e6963d2ad442e1202cced161b0546 Mon Sep 17 00:00:00 2001 From: East_Arctica Date: Thu, 16 Jan 2025 13:48:58 -0500 Subject: [PATCH 36/47] Abstract transparency coremod functions --- src/renderer/coremods/transparency/index.ts | 61 ++++++++++++--------- 1 file changed, 35 insertions(+), 26 deletions(-) diff --git a/src/renderer/coremods/transparency/index.ts b/src/renderer/coremods/transparency/index.ts index 4c582877b..ef2405fd3 100644 --- a/src/renderer/coremods/transparency/index.ts +++ b/src/renderer/coremods/transparency/index.ts @@ -1,4 +1,5 @@ import { Logger } from "@replugged"; +import { generalSettings } from "../settings/pages/General"; let observer: MutationObserver; @@ -16,6 +17,38 @@ function getRootStringProperty(property: string): string { const logger = Logger.coremod("Transparency"); +async function updateBackgroundMaterial(): Promise { + if (generalSettings.get("overrideWindowBackgroundMaterial")) return; + + const backgroundMaterial = getRootStringProperty("--window-background-material"); + if (backgroundMaterial !== (await RepluggedNative.transparency.getBackgroundMaterial())) { + logger.log("Setting background material to:", backgroundMaterial); + // @ts-expect-error @todo: Check if the transparency effect is valid? + await RepluggedNative.transparency.setBackgroundMaterial(backgroundMaterial); + } +} + +async function updateBackgroundColor(): Promise { + if (generalSettings.get("overrideWindowBackgroundColor")) return; + + const backgroundColor = getRootProperty("--window-background-color"); + if (backgroundColor !== (await RepluggedNative.transparency.getBackgroundColor())) { + logger.log("Setting background color to:", backgroundColor); + await RepluggedNative.transparency.setBackgroundColor(backgroundColor); + } +} + +async function updateVibrancy(): Promise { + if (generalSettings.get("overrideWindowBackgroundMaterial")) return; + + const vibrancy = getRootStringProperty("--window-vibrancy"); + if (vibrancy !== (await RepluggedNative.transparency.getVibrancy())) { + logger.log("Setting vibrancy effect to:", vibrancy); + // @ts-expect-error @todo: Check if the vibrancy is valid? + await RepluggedNative.transparency.setVibrancy(vibrancy); + } +} + export function start(): void { let html = document.body.parentElement!; @@ -41,38 +74,14 @@ export function start(): void { } if (cssModified) { - // Originally this used requestAnimationFrame but it took too long - // so instead we setTimeout and pray. The setTimeout could be - // shorter if we wanted, but it's hard to say if it would - // work as consistently. setTimeout(async () => { switch (DiscordNative.process.platform) { case "win32": { - const backgroundMaterial = getRootStringProperty("--window-background-material"); - if ( - backgroundMaterial !== (await RepluggedNative.transparency.getBackgroundMaterial()) - ) { - logger.log("Setting background material to:", backgroundMaterial); - // @ts-expect-error @todo: Check if the transparency effect is valid? - await RepluggedNative.transparency.setBackgroundMaterial(backgroundMaterial); - } - - const backgroundColor = getRootProperty("--window-background-color"); - if (backgroundColor !== (await RepluggedNative.transparency.getBackgroundColor())) { - logger.log("Setting background color to:", backgroundColor); - await RepluggedNative.transparency.setBackgroundColor(backgroundColor); - } + await Promise.all([updateBackgroundMaterial(), updateBackgroundColor()]); break; } case "darwin": { - const vibrancy = getRootStringProperty("--window-vibrancy"); - if (vibrancy === (await RepluggedNative.transparency.getVibrancy())) { - break; - } - - logger.log("Setting vibrancy effect to:", vibrancy); - // @ts-expect-error @todo: Check if the vibrancy is valid? - await RepluggedNative.transparency.setVibrancy(vibrancy); + await updateVibrancy(); break; } } From e18ff13edc0d36710e496548e8e79222d85ec7dc Mon Sep 17 00:00:00 2001 From: East_Arctica Date: Thu, 16 Jan 2025 15:35:26 -0500 Subject: [PATCH 37/47] Export transparency coremod functions --- src/renderer/coremods/transparency/index.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/renderer/coremods/transparency/index.ts b/src/renderer/coremods/transparency/index.ts index ef2405fd3..181f0953d 100644 --- a/src/renderer/coremods/transparency/index.ts +++ b/src/renderer/coremods/transparency/index.ts @@ -1,5 +1,11 @@ import { Logger } from "@replugged"; -import { generalSettings } from "../settings/pages/General"; +import { type GeneralSettings, defaultSettings } from "src/types"; +import * as settings from "../../apis/settings"; + +const generalSettings = await settings.init( + "dev.replugged.Settings", + defaultSettings, +); let observer: MutationObserver; @@ -17,7 +23,7 @@ function getRootStringProperty(property: string): string { const logger = Logger.coremod("Transparency"); -async function updateBackgroundMaterial(): Promise { +export async function updateBackgroundMaterial(): Promise { if (generalSettings.get("overrideWindowBackgroundMaterial")) return; const backgroundMaterial = getRootStringProperty("--window-background-material"); @@ -28,7 +34,7 @@ async function updateBackgroundMaterial(): Promise { } } -async function updateBackgroundColor(): Promise { +export async function updateBackgroundColor(): Promise { if (generalSettings.get("overrideWindowBackgroundColor")) return; const backgroundColor = getRootProperty("--window-background-color"); @@ -38,7 +44,7 @@ async function updateBackgroundColor(): Promise { } } -async function updateVibrancy(): Promise { +export async function updateVibrancy(): Promise { if (generalSettings.get("overrideWindowBackgroundMaterial")) return; const vibrancy = getRootStringProperty("--window-vibrancy"); From 744e991640350677111173c727c9d32982bc7c4a Mon Sep 17 00:00:00 2001 From: East_Arctica Date: Thu, 16 Jan 2025 16:13:49 -0500 Subject: [PATCH 38/47] Implement override settings for transparency --- .../coremods/settings/pages/General.tsx | 322 ++++++++++++++++-- src/types/settings.ts | 12 + 2 files changed, 310 insertions(+), 24 deletions(-) diff --git a/src/renderer/coremods/settings/pages/General.tsx b/src/renderer/coremods/settings/pages/General.tsx index e687191f8..adebd44da 100644 --- a/src/renderer/coremods/settings/pages/General.tsx +++ b/src/renderer/coremods/settings/pages/General.tsx @@ -9,6 +9,7 @@ import { Flex, FormItem, Notice, + SelectItem, SwitchItem, Text, TextInput, @@ -19,6 +20,14 @@ import { type GeneralSettings, defaultSettings } from "src/types"; import * as settings from "../../../apis/settings"; import * as util from "../../../util"; import { initWs, socket } from "../../devCompanion"; +import ColorPicker from "@components/ColorPicker"; +import ColorPickerCustomButton from "@components/ColorPickerCustomButton"; +import ColorPickerDefaultButton from "@components/ColorPickerDefaultButton"; +import { + updateBackgroundColor, + updateBackgroundMaterial, + updateVibrancy, +} from "../../transparency"; export const generalSettings = await settings.init( "dev.replugged.Settings", @@ -50,18 +59,30 @@ function restartModal(doRelaunch = false, onConfirm?: () => void, onCancel?: () } export const General = (): React.ReactElement => { - const { value: expValue, onChange: expOnChange } = util.useSetting( + const [expValue, expOnChange] = util.useSettingArray(generalSettings, "experiments"); + const [rdtValue, rdtOnChange] = util.useSettingArray(generalSettings, "reactDevTools"); + const [transValue, transOnChange] = util.useSettingArray(generalSettings, "transparentWindow"); + const [overrideBgColValue, overrideBgColOnChange] = util.useSettingArray( + generalSettings, + "overrideWindowBackgroundColor", + ); + const [bgColValue, bgColOnChange] = util.useSettingArray( + generalSettings, + "windowBackgroundColor", + ); + const [overrideBgMatValue, overrideBgMatOnChange] = util.useSettingArray( generalSettings, - "experiments", + "overrideWindowBackgroundMaterial", ); - const { value: rdtValue, onChange: rdtOnChange } = util.useSetting( + const [bgMatValue, bgMatOnChange] = util.useSettingArray( generalSettings, - "reactDevTools", + "windowBackgroundMaterial", ); - const { value: transValue, onChange: transOnChange } = util.useSetting( + const [overrideVibrancyValue, overrideVibrancyOnChange] = util.useSettingArray( generalSettings, - "transparentWindow", + "overrideWindowVibrancy", ); + const [vibrancyValue, vibrancyOnChange] = util.useSettingArray(generalSettings, "windowVibrancy"); const [kKeys, setKKeys] = React.useState([]); @@ -115,25 +136,278 @@ export const General = (): React.ReactElement => { {intl.string(t.REPLUGGED_SETTINGS_QUICKCSS_AUTO_APPLY)} -

- {(DiscordNative.process.platform === "linux" || - DiscordNative.process.platform === "win32") && ( - - {DiscordNative.process.platform === "linux" - ? intl.format(t.REPLUGGED_SETTINGS_TRANSPARENT_ISSUES_LINUX, {}) - : intl.format(t.REPLUGGED_SETTINGS_TRANSPARENT_ISSUES_WINDOWS, {})} - + + note="Window transparency settings"> +
+ {(DiscordNative.process.platform === "linux" || + DiscordNative.process.platform === "win32") && ( + + {DiscordNative.process.platform === "linux" + ? intl.format(t.REPLUGGED_SETTINGS_TRANSPARENT_ISSUES_LINUX, {}) + : intl.format(t.REPLUGGED_SETTINGS_TRANSPARENT_ISSUES_WINDOWS, {})} + + )} +
+ { + transOnChange(value); + restartModal(true); + }} + note={intl.format(t.REPLUGGED_SETTINGS_TRANSPARENT_DESC, {})}> + {intl.string(t.REPLUGGED_SETTINGS_TRANSPARENT)} + + + {DiscordNative.process.platform === "win32" && ( + <> + { + overrideBgColOnChange(value); + + if (value) { + void RepluggedNative.transparency.setBackgroundColor(bgColValue); + } else { + void updateBackgroundColor(); + } + }} + disabled={!transValue}> + {/* {intl.string(t.REPLUGGED_SETTINGS_OVERRIDE_BG_COLOR)} */} + Override Window Background Color + + + { + const newValue = `#${value.toString(16).padStart(6, "0").padEnd(8, "0")}`; + bgColOnChange(newValue); + void RepluggedNative.transparency.setBackgroundColor(newValue); + }} + disabled={!generalSettings.get("overrideWindowBackgroundColor") || !transValue} + customPickerPosition="right" + renderCustomButton={React.useCallback((props: object) => { + const button = ; + // return disabled ? button : + return button; + }, [])} + renderDefaultButton={React.useCallback( + (props: object) => ( + // < + + ), + [], + )} + /> + + + { + overrideBgMatOnChange(value); + if (value) { + void RepluggedNative.transparency.setBackgroundMaterial( + bgMatValue as "none" | "acrylic" | "mica" | "tabbed", + ); + } else { + // If this line is uncommented, coremods kinda die + void updateBackgroundMaterial(); + } + }} + disabled={!transValue}> + {/* {intl.string(t.REPLUGGED_SETTINGS_OVERRIDE_BG_MATERIAL)} */} + Override Window Background Material + + { + bgMatOnChange(value); + void RepluggedNative.transparency.setBackgroundMaterial( + value as "none" | "acrylic" | "mica" | "tabbed", + ); + }} + disabled={!generalSettings.get("overrideWindowBackgroundMaterial") || !transValue} + options={[ + { + label: "None", + value: "none", + }, + { + label: "Acrylic", + value: "acrylic", + }, + { + label: "Mica", + value: "mica", + }, + { + label: "Tabbed", + value: "tabbed", + }, + ]}> + Background Material + + )} -
- { - transOnChange(value); - restartModal(true); - }} - note={intl.format(t.REPLUGGED_SETTINGS_TRANSPARENT_DESC, {})}> - {intl.string(t.REPLUGGED_SETTINGS_TRANSPARENT)} - + + {DiscordNative.process.platform === "darwin" && ( + <> + {/* @todo: This should trigger the SelectItem's onChange with it's current value to apply the overridden material */} + { + overrideVibrancyOnChange(overrideVibrancyValue); + if (value) { + void RepluggedNative.transparency.setVibrancy( + vibrancyValue as + | "appearance-based" + | "light" + | "dark" + | "titlebar" + | "selection" + | "menu" + | "popover" + | "sidebar" + | "medium-light" + | "ultra-dark" + | "header" + | "sheet" + | "window" + | "hud" + | "fullscreen-ui" + | "tooltip" + | "content" + | "under-window" + | "under-page", + ); + } else { + void updateVibrancy(); + } + }} + disabled={!transValue}> + {/* {intl.string(t.REPLUGGED_SETTINGS_OVERRIDE_VIBRANCY)} */} + Override Window Vibrancy + + { + vibrancyOnChange(value); + void RepluggedNative.transparency.setVibrancy( + value as + | "appearance-based" + | "light" + | "dark" + | "titlebar" + | "selection" + | "menu" + | "popover" + | "sidebar" + | "medium-light" + | "ultra-dark" + | "header" + | "sheet" + | "window" + | "hud" + | "fullscreen-ui" + | "tooltip" + | "content" + | "under-window" + | "under-page", + ); + }} + disabled={!generalSettings.get("overrideWindowVibrancy") || !transValue} + options={[ + { + label: "Appearance-based", + value: "appearance-based", + }, + { + label: "Light", + value: "light", + }, + { + label: "Dark", + value: "dark", + }, + { + label: "Titlebar", + value: "titlebar", + }, + { + label: "Selection", + value: "selection", + }, + { + label: "Menu", + value: "menu", + }, + { + label: "Popover", + value: "popover", + }, + { + label: "Sidebar", + value: "sidebar", + }, + { + label: "Medium Light", + value: "medium-light", + }, + { + label: "Ultra Dark", + value: "ultra-dark", + }, + { + label: "Header", + value: "header", + }, + { + label: "Sheet", + value: "sheet", + }, + { + label: "Window", + value: "window", + }, + { + label: "HUD", + value: "hud", + }, + { + label: "Fullscreen UI", + value: "fullscreen-ui", + }, + { + label: "Tooltip", + value: "tooltip", + }, + { + label: "Content", + value: "content", + }, + { + label: "Under Window", + value: "under-window", + }, + { + label: "Under Page", + value: "under-page", + }, + ]}> + Vibrancy + + + )} +
; From 693efd7272cd0794e766a10be95aed03e262da7f Mon Sep 17 00:00:00 2001 From: East_Arctica Date: Thu, 16 Jan 2025 16:16:56 -0500 Subject: [PATCH 39/47] Prettier --- src/main/index.ts | 19 ++++++++++++------- .../modules/components/ColorPicker.ts | 18 +++++++++--------- .../components/ColorPickerCustomButton.ts | 6 +++--- .../components/ColorPickerDefaultButton.ts | 6 +++--- 4 files changed, 27 insertions(+), 22 deletions(-) diff --git a/src/main/index.ts b/src/main/index.ts index 4d26f57a3..0a9f3d9ee 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -102,25 +102,30 @@ class BrowserWindow extends electron.BrowserWindow { let lastLastBounds = { width: primaryDisplaySize.width * 0.75, height: primaryDisplaySize.height * 0.75, - x: primaryDisplaySize.width / 2 - primaryDisplaySize.width * 0.75 / 2, - y: primaryDisplaySize.height / 2 - primaryDisplaySize.height * 0.75 / 2, + x: primaryDisplaySize.width / 2 - (primaryDisplaySize.width * 0.75) / 2, + y: primaryDisplaySize.height / 2 - (primaryDisplaySize.height * 0.75) / 2, }; let lastResize = Date.now(); - this.on('resize', () => { + this.on("resize", () => { const bounds = this.getBounds(); lastLastBounds = lastBounds; lastBounds = bounds; lastResize = Date.now(); }); - this.on('maximize', () => { + this.on("maximize", () => { // Get the display at the center of the window const screenBounds = this.getBounds(); - const windowDisplay = electron.screen.getDisplayNearestPoint({ x: screenBounds.x + screenBounds.width / 2, y: screenBounds.y + screenBounds.height / 2 }); + const windowDisplay = electron.screen.getDisplayNearestPoint({ + x: screenBounds.x + screenBounds.width / 2, + y: screenBounds.y + screenBounds.height / 2, + }); const workAreaSize = windowDisplay.workArea; - const isSizeMaximized = lastBounds.width === workAreaSize.width && lastBounds.height === workAreaSize.height; - const isPositionMaximized = (lastBounds.x === workAreaSize.x + 1 && lastBounds.y === workAreaSize.y + 1); + const isSizeMaximized = + lastBounds.width === workAreaSize.width && lastBounds.height === workAreaSize.height; + const isPositionMaximized = + lastBounds.x === workAreaSize.x + 1 && lastBounds.y === workAreaSize.y + 1; // if we haven't resized in the last few ms, we probably didn't actually maximize and should instead unmaximize if (lastResize < Date.now() - 10 || (isSizeMaximized && isPositionMaximized)) { diff --git a/src/renderer/modules/components/ColorPicker.ts b/src/renderer/modules/components/ColorPicker.ts index 3d298ab28..1ee8910a5 100644 --- a/src/renderer/modules/components/ColorPicker.ts +++ b/src/renderer/modules/components/ColorPicker.ts @@ -12,16 +12,16 @@ const Positions = { } as const; interface ColorPickerProps { - className?: string, - defaultColor?: number, - customColor?: number, - colors: number[], - value?: number, - disabled?: boolean, + className?: string; + defaultColor?: number; + customColor?: number; + colors: number[]; + value?: number; + disabled?: boolean; onChange?: (value: number, name: string) => void; - renderDefaultButton: (props: object) => React.ReactElement, - renderCustomButton: (props: object) => React.ReactElement, - colorContainerClassName?: string + renderDefaultButton: (props: object) => React.ReactElement; + renderCustomButton: (props: object) => React.ReactElement; + colorContainerClassName?: string; customPickerPosition?: (typeof Positions)[keyof typeof Positions]; } diff --git a/src/renderer/modules/components/ColorPickerCustomButton.ts b/src/renderer/modules/components/ColorPickerCustomButton.ts index 08feff634..7cfc6694f 100644 --- a/src/renderer/modules/components/ColorPickerCustomButton.ts +++ b/src/renderer/modules/components/ColorPickerCustomButton.ts @@ -2,9 +2,9 @@ import type React from "react"; import components from "../common/components"; interface ColorPickerCustomButtonProps { - color?: number, - value?: number, - disabled?: boolean, + color?: number; + value?: number; + disabled?: boolean; onChange?: (value: number, name: string) => void; } diff --git a/src/renderer/modules/components/ColorPickerDefaultButton.ts b/src/renderer/modules/components/ColorPickerDefaultButton.ts index 5d05807ab..76a58dad7 100644 --- a/src/renderer/modules/components/ColorPickerDefaultButton.ts +++ b/src/renderer/modules/components/ColorPickerDefaultButton.ts @@ -2,9 +2,9 @@ import type React from "react"; import components from "../common/components"; interface ColorPickerDefaultButtonProps { - customColor?: number, - value?: number, - disabled?: boolean, + customColor?: number; + value?: number; + disabled?: boolean; "aria-label"?: string; } From fb9f6068d810d4622ddc028e2a2c9dc38a174b30 Mon Sep 17 00:00:00 2001 From: Federico <38290480+fedeericodl@users.noreply.github.com> Date: Fri, 26 Sep 2025 18:29:45 +0200 Subject: [PATCH 40/47] revert changes --- package.json | 2 +- src/globals.d.ts | 2 +- src/preload.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 1ad34df86..a06a036f5 100644 --- a/package.json +++ b/package.json @@ -102,4 +102,4 @@ "typescript": "~5.8.3", "typescript-eslint": "^8.37.0" } -} \ No newline at end of file +} diff --git a/src/globals.d.ts b/src/globals.d.ts index ff0ca8e09..fc9ab37f2 100644 --- a/src/globals.d.ts +++ b/src/globals.d.ts @@ -1,6 +1,6 @@ /// -// @todo: Scope global types to each component +// TODO: Scope global types to each component import type Lodash from "lodash"; import type { RepluggedNativeType } from "./preload"; diff --git a/src/preload.ts b/src/preload.ts index 36cb3583d..4ae8629b5 100644 --- a/src/preload.ts +++ b/src/preload.ts @@ -113,7 +113,7 @@ const RepluggedNative = { getVersion: (): string => version, - // @todo: We probably want to move these somewhere else, but I'm putting them here for now because I'm too lazy to set anything else up + // @todo We probably want to move these somewhere else, but I'm putting them here for now because I'm too lazy to set anything else up }; export type RepluggedNativeType = typeof RepluggedNative; From 98cf1134bfc61217f34be838af088e2e6bc6b1de Mon Sep 17 00:00:00 2001 From: Federico <38290480+fedeericodl@users.noreply.github.com> Date: Fri, 26 Sep 2025 18:55:39 +0200 Subject: [PATCH 41/47] refactor: transparency types --- src/main/ipc/transparency.ts | 53 +++++++-------- src/preload.ts | 19 +++--- .../coremods/settings/pages/General.tsx | 65 +++++-------------- src/renderer/coremods/transparency/index.ts | 6 +- src/renderer/managers/settings.ts | 33 ++++++---- src/types/coremods/transparency.ts | 7 ++ src/types/index.ts | 1 + 7 files changed, 78 insertions(+), 106 deletions(-) create mode 100644 src/types/coremods/transparency.ts diff --git a/src/main/ipc/transparency.ts b/src/main/ipc/transparency.ts index 02551e807..d16f3d823 100644 --- a/src/main/ipc/transparency.ts +++ b/src/main/ipc/transparency.ts @@ -1,21 +1,20 @@ import { BrowserWindow, ipcMain } from "electron"; -import { RepluggedIpcChannels } from "../../types"; +import { type BackgroundMaterialType, RepluggedIpcChannels, type VibrancyType } from "src/types"; -let backgroundMaterial: "auto" | "none" | "mica" | "acrylic" | "tabbed" | null = null; -ipcMain.handle( - RepluggedIpcChannels.GET_BACKGROUND_MATERIAL, - (): "auto" | "none" | "mica" | "acrylic" | "tabbed" | null => { - if (process.platform !== "win32") { - console.warn("GET_BACKGROUND_MATERIAL only works on Windows"); - } +const DEFAULT_BACKGROUND_COLOR = "#00000000"; - return backgroundMaterial; - }, -); +let backgroundMaterial: BackgroundMaterialType | null = null; +ipcMain.handle(RepluggedIpcChannels.GET_BACKGROUND_MATERIAL, (): BackgroundMaterialType | null => { + if (process.platform !== "win32") { + console.warn("GET_BACKGROUND_MATERIAL only works on Windows"); + } + + return backgroundMaterial; +}); ipcMain.handle( RepluggedIpcChannels.SET_BACKGROUND_MATERIAL, - (_, material: "auto" | "none" | "mica" | "acrylic" | "tabbed" | null) => { + (_, material: BackgroundMaterialType | null) => { if (process.platform !== "win32") { console.warn("SET_BACKGROUND_MATERIAL only works on Windows"); return; @@ -29,26 +28,20 @@ ipcMain.handle( }, ); -let currentVibrancy: Parameters[0] = null; -ipcMain.handle( - RepluggedIpcChannels.GET_VIBRANCY, - (): Parameters[0] => currentVibrancy, -); +let currentVibrancy: VibrancyType | null = null; +ipcMain.handle(RepluggedIpcChannels.GET_VIBRANCY, (): VibrancyType | null => currentVibrancy); -ipcMain.handle( - RepluggedIpcChannels.SET_VIBRANCY, - (_, vibrancy: Parameters[0]) => { - const windows = BrowserWindow.getAllWindows(); +ipcMain.handle(RepluggedIpcChannels.SET_VIBRANCY, (_, vibrancy: VibrancyType) => { + const windows = BrowserWindow.getAllWindows(); - windows.forEach((window) => window.setVibrancy(vibrancy)); - currentVibrancy = vibrancy; - }, -); + windows.forEach((window) => window.setVibrancy(vibrancy)); + currentVibrancy = vibrancy; +}); -let currentBackgroundColor = "#00000000"; -ipcMain.handle(RepluggedIpcChannels.GET_BACKGROUND_COLOR, (): string => { +let currentBackgroundColor = DEFAULT_BACKGROUND_COLOR; +ipcMain.handle(RepluggedIpcChannels.GET_BACKGROUND_COLOR, () => { if (process.platform !== "win32") { - console.warn("SET_BACKGROUND_COLOR only works on Windows"); + console.warn("GET_BACKGROUND_COLOR only works on Windows"); } return currentBackgroundColor; @@ -61,6 +54,6 @@ ipcMain.handle(RepluggedIpcChannels.SET_BACKGROUND_COLOR, (_, color: string | un } const windows = BrowserWindow.getAllWindows(); - windows.forEach((window) => window.setBackgroundColor(color || "#00000000")); - currentBackgroundColor = color || "#00000000"; + windows.forEach((window) => window.setBackgroundColor(color || DEFAULT_BACKGROUND_COLOR)); + currentBackgroundColor = color || DEFAULT_BACKGROUND_COLOR; }); diff --git a/src/preload.ts b/src/preload.ts index 4ae8629b5..ebd02cb0e 100644 --- a/src/preload.ts +++ b/src/preload.ts @@ -1,8 +1,9 @@ -import { BrowserWindow, contextBridge, ipcRenderer, webFrame } from "electron"; +import { contextBridge, ipcRenderer, webFrame } from "electron"; import { RepluggedIpcChannels } from "./types"; // eslint-disable-next-line no-duplicate-imports -- these are only used for types, the other import is for the actual code import type { + BackgroundMaterialType, CheckResultFailure, CheckResultSuccess, InstallResultFailure, @@ -10,6 +11,7 @@ import type { InstallerType, RepluggedPlugin, RepluggedTheme, + VibrancyType, } from "./types"; const version = ipcRenderer.sendSync(RepluggedIpcChannels.GET_REPLUGGED_VERSION); @@ -94,20 +96,17 @@ const RepluggedNative = { }, transparency: { - getBackgroundMaterial: (): Promise<"auto" | "none" | "mica" | "acrylic" | "tabbed"> => + getBackgroundMaterial: (): Promise => ipcRenderer.invoke(RepluggedIpcChannels.GET_BACKGROUND_MATERIAL), - setBackgroundMaterial: ( - effect: "auto" | "none" | "mica" | "acrylic" | "tabbed", - ): Promise => ipcRenderer.invoke(RepluggedIpcChannels.SET_BACKGROUND_MATERIAL, effect), + setBackgroundMaterial: (effect: BackgroundMaterialType): Promise => + ipcRenderer.invoke(RepluggedIpcChannels.SET_BACKGROUND_MATERIAL, effect), getBackgroundColor: (): Promise => ipcRenderer.invoke(RepluggedIpcChannels.GET_BACKGROUND_COLOR), setBackgroundColor: (color: string): Promise => ipcRenderer.invoke(RepluggedIpcChannels.SET_BACKGROUND_COLOR, color), - getVibrancy: (): Promise[0]> => - ipcRenderer.invoke(RepluggedIpcChannels.GET_VIBRANCY), - setVibrancy: ( - vibrancy: Parameters[0], - ): Promise => ipcRenderer.invoke(RepluggedIpcChannels.SET_VIBRANCY, vibrancy), + getVibrancy: (): Promise => ipcRenderer.invoke(RepluggedIpcChannels.GET_VIBRANCY), + setVibrancy: (vibrancy: VibrancyType): Promise => + ipcRenderer.invoke(RepluggedIpcChannels.SET_VIBRANCY, vibrancy), // visualEffectState does not need to be implemented until https://github.com/electron/electron/issues/25513 is implemented. }, diff --git a/src/renderer/coremods/settings/pages/General.tsx b/src/renderer/coremods/settings/pages/General.tsx index 4f0971f3c..b47c40d64 100644 --- a/src/renderer/coremods/settings/pages/General.tsx +++ b/src/renderer/coremods/settings/pages/General.tsx @@ -17,6 +17,7 @@ import { WEBSITE_URL } from "src/constants"; import { generalSettings } from "src/renderer/managers/settings"; import { t } from "src/renderer/modules/i18n"; import * as util from "src/renderer/util"; +import type { BackgroundMaterialType, VibrancyType } from "src/types"; import { initWs, socket } from "../../devCompanion"; import { updateBackgroundColor, @@ -69,25 +70,28 @@ const GeneralSettingsTabs = { GENERAL: "general", ADVANCED: "advanced" } as cons function GeneralTab(): React.ReactElement { const [quickCSSValue, quickCSSOnChange] = util.useSettingArray(generalSettings, "quickCSS"); const [titleBarValue, titleBarOnChange] = util.useSettingArray(generalSettings, "titleBar"); - const [transValue, transOnChange] = util.useSettingArray(generalSettings, "transparentWindow"); + const [transValue, transOnChange] = util.useSettingArray(generalSettings, "transparency.enabled"); const [overrideBgColValue, overrideBgColOnChange] = util.useSettingArray( generalSettings, - "overrideWindowBackgroundColor", + "transparency.overrideWindowBackgroundColor", ); - const [bgColValue] = util.useSettingArray(generalSettings, "windowBackgroundColor"); + const [bgColValue] = util.useSettingArray(generalSettings, "transparency.windowBackgroundColor"); const [overrideBgMatValue, overrideBgMatOnChange] = util.useSettingArray( generalSettings, - "overrideWindowBackgroundMaterial", + "transparency.overrideWindowBackgroundMaterial", ); const [bgMatValue, bgMatOnChange] = util.useSettingArray( generalSettings, - "windowBackgroundMaterial", + "transparency.windowBackgroundMaterial", ); const [overrideVibrancyValue, overrideVibrancyOnChange] = util.useSettingArray( generalSettings, - "overrideWindowVibrancy", + "transparency.overrideWindowVibrancy", + ); + const [vibrancyValue, vibrancyOnChange] = util.useSettingArray( + generalSettings, + "transparency.windowVibrancy", ); - const [vibrancyValue, vibrancyOnChange] = util.useSettingArray(generalSettings, "windowVibrancy"); React.useEffect(() => { if (quickCSSValue) window.replugged.quickCSS.load(); @@ -215,7 +219,7 @@ function GeneralTab(): React.ReactElement { overrideBgMatOnChange(value); if (value) { void RepluggedNative.transparency.setBackgroundMaterial( - bgMatValue as "none" | "acrylic" | "mica" | "tabbed", + bgMatValue as BackgroundMaterialType, ); } else { // If this line is uncommented, coremods kinda die @@ -231,10 +235,10 @@ function GeneralTab(): React.ReactElement { onChange={(value) => { bgMatOnChange(value); void RepluggedNative.transparency.setBackgroundMaterial( - value as "none" | "acrylic" | "mica" | "tabbed", + value as BackgroundMaterialType, ); }} - disabled={!generalSettings.get("overrideWindowBackgroundMaterial") || !transValue} + disabled={!overrideBgMatValue || !transValue} options={[ { label: "None", @@ -296,39 +300,10 @@ function GeneralTab(): React.ReactElement { value={vibrancyValue} onChange={(value) => { vibrancyOnChange(value); - void RepluggedNative.transparency.setVibrancy( - value as - | "titlebar" - | "selection" - | "menu" - | "popover" - | "sidebar" - | "header" - | "sheet" - | "window" - | "hud" - | "fullscreen-ui" - | "tooltip" - | "content" - | "under-window" - | "under-page" - | null, - ); + void RepluggedNative.transparency.setVibrancy(value as VibrancyType); }} - disabled={!generalSettings.get("overrideWindowVibrancy") || !transValue} + disabled={!overrideVibrancyValue || !transValue} options={[ - { - label: "Appearance-based", - value: "appearance-based", - }, - { - label: "Light", - value: "light", - }, - { - label: "Dark", - value: "dark", - }, { label: "Titlebar", value: "titlebar", @@ -349,14 +324,6 @@ function GeneralTab(): React.ReactElement { label: "Sidebar", value: "sidebar", }, - { - label: "Medium Light", - value: "medium-light", - }, - { - label: "Ultra Dark", - value: "ultra-dark", - }, { label: "Header", value: "header", diff --git a/src/renderer/coremods/transparency/index.ts b/src/renderer/coremods/transparency/index.ts index dc364f913..93bcf07e6 100644 --- a/src/renderer/coremods/transparency/index.ts +++ b/src/renderer/coremods/transparency/index.ts @@ -18,7 +18,7 @@ function getRootStringProperty(property: string): string { const logger = Logger.coremod("Transparency"); export async function updateBackgroundMaterial(): Promise { - if (generalSettings.get("overrideWindowBackgroundMaterial")) return; + if (generalSettings.get("transparency").overrideWindowBackgroundMaterial) return; const backgroundMaterial = getRootStringProperty("--window-background-material"); if (backgroundMaterial !== (await RepluggedNative.transparency.getBackgroundMaterial())) { @@ -29,7 +29,7 @@ export async function updateBackgroundMaterial(): Promise { } export async function updateBackgroundColor(): Promise { - if (generalSettings.get("overrideWindowBackgroundColor")) return; + if (generalSettings.get("transparency").overrideWindowBackgroundColor) return; const backgroundColor = getRootProperty("--window-background-color"); if (backgroundColor !== (await RepluggedNative.transparency.getBackgroundColor())) { @@ -39,7 +39,7 @@ export async function updateBackgroundColor(): Promise { } export async function updateVibrancy(): Promise { - if (generalSettings.get("overrideWindowBackgroundMaterial")) return; + if (generalSettings.get("transparency").overrideWindowBackgroundMaterial) return; const vibrancy = getRootStringProperty("--window-vibrancy"); if (vibrancy !== (await RepluggedNative.transparency.getVibrancy())) { diff --git a/src/renderer/managers/settings.ts b/src/renderer/managers/settings.ts index f0d5e378d..3a6cafec6 100644 --- a/src/renderer/managers/settings.ts +++ b/src/renderer/managers/settings.ts @@ -1,5 +1,6 @@ import { WEBSITE_URL } from "src/constants"; import { init } from "src/renderer/apis/settings"; +import type { BackgroundMaterialType, VibrancyType } from "src/types"; // eslint-disable-next-line @typescript-eslint/consistent-type-definitions export type GeneralSettings = { @@ -14,13 +15,15 @@ export type GeneralSettings = { titleBar?: boolean; quickCSS?: boolean; keepToken?: boolean; - transparentWindow?: boolean; - overrideWindowBackgroundColor?: boolean; - windowBackgroundColor?: string; - overrideWindowBackgroundMaterial?: boolean; - windowBackgroundMaterial?: string; - overrideWindowVibrancy?: boolean; - windowVibrancy?: string; + transparency?: { + enabled?: boolean; + overrideWindowBackgroundColor?: boolean; + windowBackgroundColor?: string; + overrideWindowBackgroundMaterial?: boolean; + windowBackgroundMaterial?: BackgroundMaterialType; + overrideWindowVibrancy?: boolean; + windowVibrancy?: VibrancyType; + }; }; const defaultSettings = { @@ -35,13 +38,15 @@ const defaultSettings = { titleBar: false, quickCSS: true, keepToken: false, - transparentWindow: false, - overrideWindowBackgroundColor: false, - windowBackgroundColor: "#00000000", - overrideWindowBackgroundMaterial: false, - windowBackgroundMaterial: "none", - overrideWindowVibrancy: false, - windowVibrancy: "appearance-based", + transparency: { + enabled: false, + overrideWindowBackgroundColor: false, + windowBackgroundColor: "#00000000", + overrideWindowBackgroundMaterial: false, + windowBackgroundMaterial: "none", + overrideWindowVibrancy: false, + windowVibrancy: "content", + }, } satisfies Partial; export const generalSettings = init( diff --git a/src/types/coremods/transparency.ts b/src/types/coremods/transparency.ts new file mode 100644 index 000000000..02b77a1ba --- /dev/null +++ b/src/types/coremods/transparency.ts @@ -0,0 +1,7 @@ +import { BrowserWindow } from "electron"; + +export type BackgroundMaterialType = Parameters< + typeof BrowserWindow.prototype.setBackgroundMaterial +>[0]; + +export type VibrancyType = Parameters[0]; diff --git a/src/types/index.ts b/src/types/index.ts index fd725f953..b79990052 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -67,6 +67,7 @@ export * from "./coremods/commands"; export * from "./coremods/contextMenu"; export * from "./coremods/message"; export * from "./coremods/settings"; +export * from "./coremods/transparency"; export * from "./discord"; export * from "./installer"; export * from "./settings"; From 8e163c04af2f8f1b3a7e4f644c271b822dfb4e21 Mon Sep 17 00:00:00 2001 From: Federico <38290480+fedeericodl@users.noreply.github.com> Date: Fri, 3 Oct 2025 20:28:45 +0200 Subject: [PATCH 42/47] feat: rework transparency impl --- cspell.json | 5 +- i18n/en-US.messages.d.ts | 56 ++- i18n/en-US.messages.js | 10 +- i18n/messages/cs.messages.json | 4 +- i18n/messages/de.messages.json | 4 +- i18n/messages/en-GB.messages.json | 4 +- i18n/messages/en-US.messages.json | 10 +- i18n/messages/fi.messages.json | 4 +- i18n/messages/fr.messages.json | 2 +- i18n/messages/it.messages.json | 4 +- i18n/messages/ja.messages.json | 4 +- i18n/messages/pl.messages.json | 4 +- i18n/messages/pt-BR.messages.json | 4 +- i18n/messages/ru.messages.json | 4 +- i18n/messages/tr.messages.json | 4 +- i18n/messages/zh-TW.messages.json | 4 +- src/main/index.ts | 69 +--- src/main/ipc/transparency.ts | 22 -- src/renderer/coremods/installer/util.tsx | 4 +- .../coremods/settings/pages/General.tsx | 321 ++++-------------- src/renderer/coremods/transparency/index.ts | 105 ------ src/renderer/managers/coremods.ts | 2 - src/renderer/managers/plugins.ts | 2 +- src/renderer/managers/settings.ts | 16 +- src/types/coremods/transparency.ts | 25 +- 25 files changed, 193 insertions(+), 500 deletions(-) delete mode 100644 src/renderer/coremods/transparency/index.ts diff --git a/cspell.json b/cspell.json index 479affb2e..e5946152e 100644 --- a/cspell.json +++ b/cspell.json @@ -14,8 +14,8 @@ "cooldown", "coremod", "coremods", - "customitem", "crosspost", + "customitem", "dependants", "devmode", "discordapp", @@ -32,8 +32,8 @@ "installdir", "jsona", "Jsonifiable", - "konami", "keybind", + "konami", "leaderboard", "lezer", "LOCALAPPDATA", @@ -59,6 +59,7 @@ "signingkey", "Skema", "skus", + "titlebar", "uninject", "uninjector", "uninjectors", diff --git a/i18n/en-US.messages.d.ts b/i18n/en-US.messages.d.ts index bc2496b8b..a59dd9f7a 100644 --- a/i18n/en-US.messages.d.ts +++ b/i18n/en-US.messages.d.ts @@ -1737,6 +1737,58 @@ export declare const messages: { * Missing translations: `bg`, `da`, `el`, `es-419`, `hi`, `hr`, `hu`, `ko`, `lt`, `nl`, `no`, `ro`, `sv-SE`, `th`, `zh-CN` */ 'REPLUGGED_SETTINGS_RESTART_TITLE': TypedIntlMessageGetter<{}>, + /** + * Key: `ujAx5O` + * + * ### Definition + * ```text + * Transparency + * ``` + * + * ### Problems + * + * Missing translations: `bg`, `cs`, `da`, `de`, `el`, `en-GB`, `es-419`, `es-ES`, `fi`, `fr`, `hi`, `hr`, `hu`, `it`, `ja`, `ko`, `lt`, `nl`, `no`, `pl`, `pt-BR`, `ro`, `ru`, `sv-SE`, `th`, `tr`, `uk`, `vi`, `zh-CN`, `zh-TW` + */ + 'REPLUGGED_SETTINGS_TRANSPARENCY': TypedIntlMessageGetter<{}>, + /** + * Key: `e6ACBQ` + * + * ### Definition + * ```text + * Background Material + * ``` + * + * ### Problems + * + * Missing translations: `bg`, `cs`, `da`, `de`, `el`, `en-GB`, `es-419`, `es-ES`, `fi`, `fr`, `hi`, `hr`, `hu`, `it`, `ja`, `ko`, `lt`, `nl`, `no`, `pl`, `pt-BR`, `ro`, `ru`, `sv-SE`, `th`, `tr`, `uk`, `vi`, `zh-CN`, `zh-TW` + */ + 'REPLUGGED_SETTINGS_TRANSPARENCY_BG_MATERIAL': TypedIntlMessageGetter<{}>, + /** + * Key: `eCCNpa` + * + * ### Definition + * ```text + * Manage transparency and visual effects for the Discord window. + * ``` + * + * ### Problems + * + * Missing translations: `bg`, `cs`, `da`, `de`, `el`, `en-GB`, `es-419`, `es-ES`, `fi`, `fr`, `hi`, `hr`, `hu`, `it`, `ja`, `ko`, `lt`, `nl`, `no`, `pl`, `pt-BR`, `ro`, `ru`, `sv-SE`, `th`, `tr`, `uk`, `vi`, `zh-CN`, `zh-TW` + */ + 'REPLUGGED_SETTINGS_TRANSPARENCY_DESC': TypedIntlMessageGetter<{}>, + /** + * Key: `sO01jI` + * + * ### Definition + * ```text + * Vibrancy + * ``` + * + * ### Problems + * + * Missing translations: `bg`, `cs`, `da`, `de`, `el`, `en-GB`, `es-419`, `es-ES`, `fi`, `fr`, `hi`, `hr`, `hu`, `it`, `ja`, `ko`, `lt`, `nl`, `no`, `pl`, `pt-BR`, `ro`, `ru`, `sv-SE`, `th`, `tr`, `uk`, `vi`, `zh-CN`, `zh-TW` + */ + 'REPLUGGED_SETTINGS_TRANSPARENCY_VIBRANCY': TypedIntlMessageGetter<{}>, /** * Key: `F1Fb1N` * @@ -1768,7 +1820,7 @@ export declare const messages: { * * ### Definition * ```text - * ****WARNING:**** **Hardware acceleration** may need to be turned **off**. In some cases, you may experience a black background, such as when the window is cut off at the top or bottom due to the monitor resolution, or when the development tools are open and docked. + * **Hardware acceleration** may need to be turned **off**. In some cases, you may experience a black background, such as when the window is cut off at the top or bottom due to the monitor resolution, or when the development tools are open and docked. * ``` * * ### Problems @@ -1781,7 +1833,7 @@ export declare const messages: { * * ### Definition * ```text - * ****WARNING:**** This will break **window snapping**. In some cases, you may experience a black background, such as when the window is cut off at the top or bottom due to the monitor resolution, or when the development tools are open and docked. + * This will break **window snapping**. In some cases, you may experience a black background, such as when the window is cut off at the top or bottom due to the monitor resolution, or when the development tools are open and docked. * ``` * * ### Problems diff --git a/i18n/en-US.messages.js b/i18n/en-US.messages.js index 7c1e1b0d5..a62e0b4b1 100644 --- a/i18n/en-US.messages.js +++ b/i18n/en-US.messages.js @@ -229,8 +229,8 @@ export default defineMessages({ "REPLUGGED_SETTINGS_RESTART_TITLE": "Restart Required", "REPLUGGED_SETTINGS_TRANSPARENT": "Transparent Window", "REPLUGGED_SETTINGS_TRANSPARENT_DESC": "Makes the Discord window transparent, primarily useful for theming. **Requires restart**.", - "REPLUGGED_SETTINGS_TRANSPARENT_ISSUES_WINDOWS": "****WARNING:**** This will break **window snapping**. In some cases, you may experience a black background, such as when the window is cut off at the top or bottom due to the monitor resolution, or when the development tools are open and docked.", - "REPLUGGED_SETTINGS_TRANSPARENT_ISSUES_LINUX": "****WARNING:**** **Hardware acceleration** may need to be turned **off**. In some cases, you may experience a black background, such as when the window is cut off at the top or bottom due to the monitor resolution, or when the development tools are open and docked.", + "REPLUGGED_SETTINGS_TRANSPARENT_ISSUES_WINDOWS": "This will break **window snapping**. In some cases, you may experience a black background, such as when the window is cut off at the top or bottom due to the monitor resolution, or when the development tools are open and docked.", + "REPLUGGED_SETTINGS_TRANSPARENT_ISSUES_LINUX": "**Hardware acceleration** may need to be turned **off**. In some cases, you may experience a black background, such as when the window is cut off at the top or bottom due to the monitor resolution, or when the development tools are open and docked.", "REPLUGGED_SETTINGS_ERROR_PLUGIN_NAME": "Plugin: {name}", "REPLUGGED_STORE": "Store", "REPLUGGED_SETTINGS_CUSTOM_TITLE_BAR": "Custom Title Bar", @@ -241,5 +241,9 @@ export default defineMessages({ "REPLUGGED_SETTINGS_QUICKCSS_ENABLE_DESC": "Apply custom styles to Discord instantly. Change colors, layout, and appearance in real time without installing themes.", "REPLUGGED_ADDON_SETTINGS_THEME_PRESET": "Choose Theme Preset", "REPLUGGED_TOAST_THEME_PRESET_CHANGED": "Switched to preset: {name}", - "REPLUGGED_TOAST_THEME_PRESET_FAILED": "Failed to change preset for {name}" + "REPLUGGED_TOAST_THEME_PRESET_FAILED": "Failed to change preset for {name}", + "REPLUGGED_SETTINGS_TRANSPARENCY": "Transparency", + "REPLUGGED_SETTINGS_TRANSPARENCY_DESC": "Manage transparency and visual effects for the Discord window.", + "REPLUGGED_SETTINGS_TRANSPARENCY_BG_MATERIAL": "Background Material", + "REPLUGGED_SETTINGS_TRANSPARENCY_VIBRANCY": "Vibrancy" }); \ No newline at end of file diff --git a/i18n/messages/cs.messages.json b/i18n/messages/cs.messages.json index 7d8e8c3de..954101d47 100644 --- a/i18n/messages/cs.messages.json +++ b/i18n/messages/cs.messages.json @@ -178,7 +178,7 @@ "REPLUGGED_SETTINGS_ADDON_EMBEDS": "Zobrazit vložený obsash přídavných modulů", "REPLUGGED_SETTINGS_ADDON_EMBEDS_DESC": "Zobrazit kartu s informacemi pro přídavný modul když je v chatu sdílen instalační odkaz.", "REPLUGGED_SETTINGS_REACT_DEVTOOLS": "Povolit React DevTools", - "REPLUGGED_SETTINGS_TRANSPARENT_ISSUES_WINDOWS": "****VAROVÁNÍ:**** Povolení tohoto nastavení znefukční **přichycování oken**. V některých případech uvidíš černé pozadí, například když je část okna odříznuta nahoře nebo dole kvůli rozlišení monitoru nebo když jsou otevřené a ukotvené vývojářské nástroje.", + "REPLUGGED_SETTINGS_TRANSPARENT_ISSUES_WINDOWS": "Povolení tohoto nastavení znefukční **přichycování oken**. V některých případech uvidíš černé pozadí, například když je část okna odříznuta nahoře nebo dole kvůli rozlišení monitoru nebo když jsou otevřené a ukotvené vývojářské nástroje.", "REPLUGGED_COMMAND_ERROR_GENERIC": "Nastala chyba, prosím, zkus to znovu později. Pokud tento problém přetrvává, kontaktuj prosím tým Replugged.", "REPLUGGED_COMMAND_ENABLE_NAME": "povolit", "REPLUGGED_COMMAND_ENABLE_OPTION_ADDON_NAME": "doplněk", @@ -222,7 +222,7 @@ "REPLUGGED_COMMAND_LIST_OPTION_STATUS_NAME": "stav", "REPLUGGED_COMMAND_LIST_OPTION_STATUS_CHOICE_DISABLED": "Zakázané", "REPLUGGED_COMMAND_LIST_HEADER_DISABLED": "Zakázané {type}", - "REPLUGGED_SETTINGS_TRANSPARENT_ISSUES_LINUX": "****VAROVÁNÍ:**** Je možné, že bude nutné **vypnout hardwarovou akceleraci**. V některých případech uvidíš černé pozadí, například když je část okna odříznuta nahoře nebo dole kvůli rozlišení monitoru nebo když jsou otevřené a ukotvené vývojářské nástroje.", + "REPLUGGED_SETTINGS_TRANSPARENT_ISSUES_LINUX": "Je možné, že bude nutné **vypnout hardwarovou akceleraci**. V některých případech uvidíš černé pozadí, například když je část okna odříznuta nahoře nebo dole kvůli rozlišení monitoru nebo když jsou otevřené a ukotvené vývojářské nástroje.", "REPLUGGED_COMMAND_LIST_ERROR_SPECIFY": "Musíš specifikovat, jestli mám poslat seznam pluginů, nebo motivů", "REPLUGGED_SETTINGS_TRANSPARENT_DESC": "Zprůhlední okno Discordu, užitečné zejména pro tvorbu motivů. **Vyžaduje restart**." } diff --git a/i18n/messages/de.messages.json b/i18n/messages/de.messages.json index 57cd27371..4107c0d12 100644 --- a/i18n/messages/de.messages.json +++ b/i18n/messages/de.messages.json @@ -192,7 +192,7 @@ "REPLUGGED_COMMAND_LIST_DESC": "Liste alle Plugins und Themes", "REPLUGGED_COMMAND_LIST_OPTION_SEND_NAME": "senden", "REPLUGGED_SETTINGS_TRANSPARENT": "Transparentes Fenster", - "REPLUGGED_SETTINGS_TRANSPARENT_ISSUES_LINUX": "****WARNUNG:**** **Hardware acceleration** muss gegebenenfalls **ausgeschaltet** werden. In manchen Fällen, ist ein schwarzer Hintergrund zu erwarten, wie wenn das Fenster am unteren oder oberen Rand durch die Monitor Auflösung abgeschnitten wird oder wie wenn die Developer Tools offen und angedockt sind.", + "REPLUGGED_SETTINGS_TRANSPARENT_ISSUES_LINUX": "**Hardware acceleration** muss gegebenenfalls **ausgeschaltet** werden. In manchen Fällen, ist ein schwarzer Hintergrund zu erwarten, wie wenn das Fenster am unteren oder oberen Rand durch die Monitor Auflösung abgeschnitten wird oder wie wenn die Developer Tools offen und angedockt sind.", "REPLUGGED_SETTINGS_TRANSPARENT_DESC": "Mache das Discord-Fenster durchsichtig, hauptsächlich zu Nutzen bei Themes. **Benötigt Neustart**.", "REPLUGGED_COMMAND_SUCCESS_GENERIC": "Erfolgreich", "REPLUGGED_COMMAND_LIST_OPTION_SEND_DESC": "Teile diese Liste öffentlich im Chat", @@ -211,7 +211,7 @@ "REPLUGGED_COMMAND_LIST_HEADER_DISABLED": "{type} deaktiviert", "REPLUGGED_COMMAND_LIST_ERROR_SPECIFY": "Du musst angeben ob eine Plugin- oder eine Theme-Liste gesendet werden soll", "REPLUGGED_SETTINGS_ERROR_PLUGIN_NAME": "Plugin: {name}", - "REPLUGGED_SETTINGS_TRANSPARENT_ISSUES_WINDOWS": "****WARNUNG:**** Dadurch wird **das Einrasten von Fenstern** unterbrochen. In manchen Fällen kann es zu einem schwarzen Hintergrund kommen, etwa wenn das Fenster aufgrund der Monitorauflösung oben oder unten abgeschnitten ist oder wenn die Entwicklungstools geöffnet und angedockt sind.", + "REPLUGGED_SETTINGS_TRANSPARENT_ISSUES_WINDOWS": "Dadurch wird **das Einrasten von Fenstern** unterbrochen. In manchen Fällen kann es zu einem schwarzen Hintergrund kommen, etwa wenn das Fenster aufgrund der Monitorauflösung oben oder unten abgeschnitten ist oder wenn die Entwicklungstools geöffnet und angedockt sind.", "REPLUGGED_COMMAND_DISABLE_NAME": "deaktivieren", "REPLUGGED_COMMAND_ENABLE_NAME": "aktivieren", "REPLUGGED_COMMAND_RELOAD_NAME": "nachladen", diff --git a/i18n/messages/en-GB.messages.json b/i18n/messages/en-GB.messages.json index 990b35833..d030c91de 100644 --- a/i18n/messages/en-GB.messages.json +++ b/i18n/messages/en-GB.messages.json @@ -196,9 +196,9 @@ "REPLUGGED_COMMAND_LIST_HEADER_ENABLED": "Enabled {type}", "REPLUGGED_COMMAND_LIST_HEADER_DISABLED": "Disabled {type}", "REPLUGGED_COMMAND_LIST_ERROR_SPECIFY": "You need to specify whether to send a plugin or theme list", - "REPLUGGED_SETTINGS_TRANSPARENT_ISSUES_WINDOWS": "****WARNING:**** This is break **window snapping**. In some cases, you make experience a black background, such as when the window is cut off at the top or bottom due to the monitor resolution, or when the development tools are open and docked.", + "REPLUGGED_SETTINGS_TRANSPARENT_ISSUES_WINDOWS": "This will break **window snapping**. In some cases, you make experience a black background, such as when the window is cut off at the top or bottom due to the monitor resolution, or when the development tools are open and docked.", "REPLUGGED_SETTINGS_TRANSPARENT": "Transparent Window", - "REPLUGGED_SETTINGS_TRANSPARENT_ISSUES_LINUX": "****WARNING:**** Hardware acceleration** may need to be turned **off**. In some cases, you may experience a black background, such as when the window is cut off at the top of bottom due to the monitor resolution, or when the development tools are opened and docked.", + "REPLUGGED_SETTINGS_TRANSPARENT_ISSUES_LINUX": "**Hardware acceleration** may need to be turned **off**. In some cases, you may experience a black background, such as when the window is cut off at the top of bottom due to the monitor resolution, or when the development tools are opened and docked.", "REPLUGGED_SETTINGS_TRANSPARENT_DESC": "Makes the Discord window transparent, primarily useful for theming. **Requires restart**.", "REPLUGGED_COMMAND_SUCCESS_GENERIC": "Success", "REPLUGGED_COMMAND_RELOAD_MESSAGE_ENABLED": "{type} {name} has been reloaded!", diff --git a/i18n/messages/en-US.messages.json b/i18n/messages/en-US.messages.json index 82588e278..f1d134003 100644 --- a/i18n/messages/en-US.messages.json +++ b/i18n/messages/en-US.messages.json @@ -224,8 +224,8 @@ "REPLUGGED_SETTINGS_RESTART_TITLE": "Restart Required", "REPLUGGED_SETTINGS_TRANSPARENT": "Transparent Window", "REPLUGGED_SETTINGS_TRANSPARENT_DESC": "Makes the Discord window transparent, primarily useful for theming. **Requires restart**.", - "REPLUGGED_SETTINGS_TRANSPARENT_ISSUES_WINDOWS": "****WARNING:**** This will break **window snapping**. In some cases, you may experience a black background, such as when the window is cut off at the top or bottom due to the monitor resolution, or when the development tools are open and docked.", - "REPLUGGED_SETTINGS_TRANSPARENT_ISSUES_LINUX": "****WARNING:**** **Hardware acceleration** may need to be turned **off**. In some cases, you may experience a black background, such as when the window is cut off at the top or bottom due to the monitor resolution, or when the development tools are open and docked.", + "REPLUGGED_SETTINGS_TRANSPARENT_ISSUES_WINDOWS": "This will break **window snapping**. In some cases, you may experience a black background, such as when the window is cut off at the top or bottom due to the monitor resolution, or when the development tools are open and docked.", + "REPLUGGED_SETTINGS_TRANSPARENT_ISSUES_LINUX": "**Hardware acceleration** may need to be turned **off**. In some cases, you may experience a black background, such as when the window is cut off at the top or bottom due to the monitor resolution, or when the development tools are open and docked.", "REPLUGGED_SETTINGS_ERROR_PLUGIN_NAME": "Plugin: {name}", "REPLUGGED_STORE": "Store", "REPLUGGED_SETTINGS_CUSTOM_TITLE_BAR": "Custom Title Bar", @@ -236,5 +236,9 @@ "REPLUGGED_SETTINGS_QUICKCSS_ENABLE_DESC": "Apply custom styles to Discord instantly. Change colors, layout, and appearance in real time without installing themes.", "REPLUGGED_ADDON_SETTINGS_THEME_PRESET": "Choose Theme Preset", "REPLUGGED_TOAST_THEME_PRESET_CHANGED": "Switched to preset: {name}", - "REPLUGGED_TOAST_THEME_PRESET_FAILED": "Failed to change preset for {name}" + "REPLUGGED_TOAST_THEME_PRESET_FAILED": "Failed to change preset for {name}", + "REPLUGGED_SETTINGS_TRANSPARENCY": "Transparency", + "REPLUGGED_SETTINGS_TRANSPARENCY_DESC": "Manage transparency and visual effects for the Discord window.", + "REPLUGGED_SETTINGS_TRANSPARENCY_BG_MATERIAL": "Background Material", + "REPLUGGED_SETTINGS_TRANSPARENCY_VIBRANCY": "Vibrancy" } diff --git a/i18n/messages/fi.messages.json b/i18n/messages/fi.messages.json index be10e3f30..d82576cd9 100644 --- a/i18n/messages/fi.messages.json +++ b/i18n/messages/fi.messages.json @@ -222,7 +222,7 @@ "REPLUGGED_COMMAND_INSTALL_OPTION_SOURCE_DESC": "Lähde josta lisäosa asennetaan", "REPLUGGED_COMMAND_INSTALL_OPTION_ADDON_DESC": "Lähteestä asennettavan lisäosan tunniste", "REPLUGGED_SETTINGS_TRANSPARENT": "Läpinäkyvä Ikkuna", - "REPLUGGED_SETTINGS_TRANSPARENT_ISSUES_LINUX": "****VAROITUS:**** **Laitteistokiihdytys** on ehkä **poistettava käytöstä**. Joissain tapauksissa saatat kokea mustan taustan, kuten jos ikkuna on leikattu ylä- tai alaosasta monitorin resoluution takia, tai kun kehitystyökalut on auki ja telakoituna.", + "REPLUGGED_SETTINGS_TRANSPARENT_ISSUES_LINUX": "**Laitteistokiihdytys** on ehkä **poistettava käytöstä**. Joissain tapauksissa saatat kokea mustan taustan, kuten jos ikkuna on leikattu ylä- tai alaosasta monitorin resoluution takia, tai kun kehitystyökalut on auki ja telakoituna.", "REPLUGGED_SETTINGS_TRANSPARENT_DESC": "Tekee Discordin ikkunasta läpinäkyvän, käytetään ensisijaisesti teemaamiseen. **Vaatii uudelleenkäynnistyksen**.", - "REPLUGGED_SETTINGS_TRANSPARENT_ISSUES_WINDOWS": "****VAROITUS:**** Tämä rikkoo **ikkunan napsahtamisen**. Joissain tapauksissa saatat kokea mustan taustan, kuten jos ikkuna on leikattu ylä- tai alaosasta monitorin resoluution takia, tai kun kehitystyökalut on auki ja telakoituna." + "REPLUGGED_SETTINGS_TRANSPARENT_ISSUES_WINDOWS": "Tämä rikkoo **ikkunan napsahtamisen**. Joissain tapauksissa saatat kokea mustan taustan, kuten jos ikkuna on leikattu ylä- tai alaosasta monitorin resoluution takia, tai kun kehitystyökalut on auki ja telakoituna." } diff --git a/i18n/messages/fr.messages.json b/i18n/messages/fr.messages.json index 48975d5a7..57edb5d59 100644 --- a/i18n/messages/fr.messages.json +++ b/i18n/messages/fr.messages.json @@ -181,7 +181,7 @@ "REPLUGGED_SETTINGS_ADDON_EMBEDS_DESC": "Afficher une carte contenant des informations sur un addon lorsqu'un lien de vente/installation est partagé dans le chat.", "REPLUGGED_SETTINGS_RESTART_TITLE": "Redémarrage requis", "REPLUGGED_SETTINGS_TRANSPARENT_DESC": "Rend la fenêtre de Discord transparente, principalement utile pour les thèmes. **Requiert un redémarrage**.", - "REPLUGGED_SETTINGS_TRANSPARENT_ISSUES_LINUX": "****AVERTISSEMENT :**** **L'accélération matérielle** doit possiblement être **désactivée**. Dans certains cas, un arrière-plan noir peut apparaître, par exemple lorsque la fenêtre est coupée en haut ou en bas à cause de la résolution de l'écran, ou lorsque les outils de développement sont ouverts et ancrés.", + "REPLUGGED_SETTINGS_TRANSPARENT_ISSUES_LINUX": "**L'accélération matérielle** doit possiblement être **désactivée**. Dans certains cas, un arrière-plan noir peut apparaître, par exemple lorsque la fenêtre est coupée en haut ou en bas à cause de la résolution de l'écran, ou lorsque les outils de développement sont ouverts et ancrés.", "REPLUGGED_SETTINGS_TRANSPARENT": "Fenêtre transparente", "REPLUGGED_COMMAND_ENABLE_MESSAGE_ENABLED": "{type} {name} a été activé !", "REPLUGGED_COMMAND_DISABLE_MESSAGE_ENABLED": "{type} {name} a été désactivé !", diff --git a/i18n/messages/it.messages.json b/i18n/messages/it.messages.json index 2442c9624..60712d815 100644 --- a/i18n/messages/it.messages.json +++ b/i18n/messages/it.messages.json @@ -180,8 +180,8 @@ "REPLUGGED_SETTINGS_ADDON_EMBEDS_DESC": "Mostra una scheda con le informazioni su un addon quando un link allo store/installazione viene condiviso in chat.", "REPLUGGED_SETTINGS_RESTART_TITLE": "Riavvio necessario", "REPLUGGED_SETTINGS_TRANSPARENT_DESC": "Rende trasparente la finestra di Discord, utile principalmente per la creazione di temi. **Necessita di un riavvio**.", - "REPLUGGED_SETTINGS_TRANSPARENT_ISSUES_WINDOWS": "****ATTENZIONE:**** Ciò interromperà **l'ancoraggio finestre**. In alcuni casi, potresti riscontrare uno sfondo nero, ad esempio quando la finestra è tagliata nella parte superiore o inferiore a causa della risoluzione del monitor o quando gli strumenti di sviluppo sono aperti e agganciati.", - "REPLUGGED_SETTINGS_TRANSPARENT_ISSUES_LINUX": "****ATTENZIONE:**** Potrebbe essere necessario disattivare **l'accelerazione hardware**. In alcuni casi, potresti riscontrare uno sfondo nero, ad esempio quando la finestra è tagliata nella parte superiore o inferiore a causa della risoluzione del monitor o quando gli strumenti di sviluppo sono aperti e agganciati.", + "REPLUGGED_SETTINGS_TRANSPARENT_ISSUES_WINDOWS": "Ciò interromperà **l'ancoraggio finestre**. In alcuni casi, potresti riscontrare uno sfondo nero, ad esempio quando la finestra è tagliata nella parte superiore o inferiore a causa della risoluzione del monitor o quando gli strumenti di sviluppo sono aperti e agganciati.", + "REPLUGGED_SETTINGS_TRANSPARENT_ISSUES_LINUX": "Potrebbe essere necessario disattivare **l'accelerazione hardware**. In alcuni casi, potresti riscontrare uno sfondo nero, ad esempio quando la finestra è tagliata nella parte superiore o inferiore a causa della risoluzione del monitor o quando gli strumenti di sviluppo sono aperti e agganciati.", "REPLUGGED_SETTINGS_TRANSPARENT": "Finestra trasparente", "REPLUGGED_COMMAND_ERROR_GENERIC": "Qualcosa è andato storto, riprova più tardi. Se il problema persiste, contatta il team di Replugged.", "REPLUGGED_COMMAND_ENABLE_NAME": "abilita", diff --git a/i18n/messages/ja.messages.json b/i18n/messages/ja.messages.json index 0178cd93c..f774f257c 100644 --- a/i18n/messages/ja.messages.json +++ b/i18n/messages/ja.messages.json @@ -147,9 +147,9 @@ "REPLUGGED_SETTINGS_ADDON_EMBEDS_DESC": "チャットでストア/インストールリンクが共有されたときに、アドオンの情報が記載されたカードを表示するようにします。", "REPLUGGED_SETTINGS_TRANSPARENT": "透明なウィンドウ", "REPLUGGED_ADDON_NOT_REVIEWED": "未審査の{type}", - "REPLUGGED_SETTINGS_TRANSPARENT_ISSUES_WINDOWS": "****警告: **** これにより **ウィンドウスナップ** が機能しなくなります。モニターの解像度の関係でウィンドウの上下が切れている場合や、開発者ツールを開いてドッキングしている場合など、背景が黒くなる場合があります。", + "REPLUGGED_SETTINGS_TRANSPARENT_ISSUES_WINDOWS": "これにより **ウィンドウスナップ** が機能しなくなります。モニターの解像度の関係でウィンドウの上下が切れている場合や、開発者ツールを開いてドッキングしている場合など、背景が黒くなる場合があります。", "REPLUGGED_ADDON_NOT_REVIEWED_DESC": "この{type}は Replugged チームによってレビューされておらず、あなたのコンピュータに損害を与える可能性があります。自己責任で使用してください。", - "REPLUGGED_SETTINGS_TRANSPARENT_ISSUES_LINUX": "****警告: **** **ハードウェアアクセラレーション** を **オフ** にする必要があるかもしれません。モニターの解像度の関係でウィンドウの上下が切れている場合や、開発者ツールを開いてドッキングしている場合など、背景が黒くなる場合があります。", + "REPLUGGED_SETTINGS_TRANSPARENT_ISSUES_LINUX": "**ハードウェアアクセラレーション** を **オフ** にする必要があるかもしれません。モニターの解像度の関係でウィンドウの上下が切れている場合や、開発者ツールを開いてドッキングしている場合など、背景が黒くなる場合があります。", "REPLUGGED_SETTINGS_QUICKCSS_AUTO_APPLY": "Quick CSS を自動的に適用", "REPLUGGED_I18N": "Replugged 翻訳", "REPLUGGED_SETTINGS_QUICKCSS_AUTO_APPLY_DESC": "入力中に Quick CSS の変更を自動的に適用します。", diff --git a/i18n/messages/pl.messages.json b/i18n/messages/pl.messages.json index 01803f998..45ed2f824 100644 --- a/i18n/messages/pl.messages.json +++ b/i18n/messages/pl.messages.json @@ -179,8 +179,8 @@ "REPLUGGED_SETTINGS_ADDON_EMBEDS": "Włącz osadzone karty dodatków", "REPLUGGED_SETTINGS_ADDON_EMBEDS_DESC": "Pokaż kartę z informacjami o dodatku, gdy link do sklepu lub instalacji jest udostępniany na czacie.", "REPLUGGED_SETTINGS_RESTART_TITLE": "Wymagane ponowne uruchomienie", - "REPLUGGED_SETTINGS_TRANSPARENT_ISSUES_LINUX": "**** UWAGA:**** **Akceleracja sprzętowa** może wymagać **wyłączenia**. W niektórych przypadkach może pojawić się czarne tło, na przykład gdy okno jest odcięte u góry lub u dołu ze względu na rozdzielczość monitora lub gdy narzędzia deweloperskie są otwarte i zadokowane.", - "REPLUGGED_SETTINGS_TRANSPARENT_ISSUES_WINDOWS": "****UWAGA:**** Włączenie tej opcji zakłóci **przyciąganie okna**. W niektórych przypadkach może pojawić się czarne tło, na przykład gdy okno jest odcięte u góry lub u dołu ze względu na rozdzielczość monitora lub gdy narzędzia deweloperskie są otwarte i zadokowane.", + "REPLUGGED_SETTINGS_TRANSPARENT_ISSUES_LINUX": "**Akceleracja sprzętowa** może wymagać **wyłączenia**. W niektórych przypadkach może pojawić się czarne tło, na przykład gdy okno jest odcięte u góry lub u dołu ze względu na rozdzielczość monitora lub gdy narzędzia deweloperskie są otwarte i zadokowane.", + "REPLUGGED_SETTINGS_TRANSPARENT_ISSUES_WINDOWS": "Włączenie tej opcji zakłóci **przyciąganie okna**. W niektórych przypadkach może pojawić się czarne tło, na przykład gdy okno jest odcięte u góry lub u dołu ze względu na rozdzielczość monitora lub gdy narzędzia deweloperskie są otwarte i zadokowane.", "REPLUGGED_SETTINGS_TRANSPARENT": "Przezroczyste okno", "REPLUGGED_SETTINGS_TRANSPARENT_DESC": "Sprawia, że okno Discorda staje się przezroczyste, co jest przydatne podczas tworzenia lub używania niektórych motywów. **Wymaga ponownego uruchomienia**.", "REPLUGGED_COMMAND_ERROR_GENERIC": "Coś poszło nie tak, spróbuj ponownie później. Jeżeli ten błąd się powtórzy, skontaktuj się z zespołem Replugged.", diff --git a/i18n/messages/pt-BR.messages.json b/i18n/messages/pt-BR.messages.json index 6ed260f8d..25f0ca7cb 100644 --- a/i18n/messages/pt-BR.messages.json +++ b/i18n/messages/pt-BR.messages.json @@ -184,8 +184,8 @@ "REPLUGGED_SETTINGS_ADDON_EMBEDS_DESC": "Apresentar um cartão com informações sobre um complemento quando um link de loja/instalação é compartilhado no chat.", "REPLUGGED_SETTINGS_RESTART_TITLE": "É necessário uma reinicialização", "REPLUGGED_SETTINGS_TRANSPARENT": "Janela Transparente", - "REPLUGGED_SETTINGS_TRANSPARENT_ISSUES_LINUX": "****ATENÇÃO:***** Pode ser necessário desativar a **Aceleração de Hardware**. Em alguns casos, você pode ter um fundo preto, como quando a janela é cortada na parte superior ou inferior devido à resolução do monitor, ou quando as ferramentas de desenvolvedor estão abertas e ancoradas.", - "REPLUGGED_SETTINGS_TRANSPARENT_ISSUES_WINDOWS": "****ATENÇÃO:**** Isso irá desativar a **alinhação automática de janelas**. Em alguns casos, você pode ter um fundo preto, como quando a janela é cortada na parte superior ou inferior devido à resolução do monitor, ou quando as ferramentas de desenvolvedor estão abertas e ancoradas..", + "REPLUGGED_SETTINGS_TRANSPARENT_ISSUES_LINUX": "Pode ser necessário desativar a **Aceleração de Hardware**. Em alguns casos, você pode ter um fundo preto, como quando a janela é cortada na parte superior ou inferior devido à resolução do monitor, ou quando as ferramentas de desenvolvedor estão abertas e ancoradas.", + "REPLUGGED_SETTINGS_TRANSPARENT_ISSUES_WINDOWS": "Isso irá desativar a **alinhação automática de janelas**. Em alguns casos, você pode ter um fundo preto, como quando a janela é cortada na parte superior ou inferior devido à resolução do monitor, ou quando as ferramentas de desenvolvedor estão abertas e ancoradas..", "REPLUGGED_SETTINGS_TRANSPARENT_DESC": "Torna a janela do Discord transparente, principalmente útil para personalização. **Necessita uma reinicialização**.", "REPLUGGED_COMMAND_ENABLE_OPTION_ADDON_NAME": "complemento", "REPLUGGED_COMMAND_ADDONS_OPTION_ADDON_DESC": "Escolha qual complemento deve ser habilitado", diff --git a/i18n/messages/ru.messages.json b/i18n/messages/ru.messages.json index adc63cbe4..55c6085db 100644 --- a/i18n/messages/ru.messages.json +++ b/i18n/messages/ru.messages.json @@ -179,8 +179,8 @@ "REPLUGGED_SETTINGS_ADDON_EMBEDS_DESC": "Посмотреть в хранилище.", "REPLUGGED_SETTINGS_ADDON_EMBEDS": "Показывать карточку с информацией об аддоне, при отправке ссылки на хранилище/установку в чате", "REPLUGGED_SETTINGS_RESTART_TITLE": "Требуется перезагрузка", - "REPLUGGED_SETTINGS_TRANSPARENT_ISSUES_WINDOWS": "****ПРЕДУПРЕЖДЕНИЕ:**** Это приведет к нарушению **привязки окна**. В некоторых случаях может появиться черный фон, например, когда окно обрезается сверху или снизу из-за разрешения монитора, или когда инструменты разработки открыты и пристыкованы.", - "REPLUGGED_SETTINGS_TRANSPARENT_ISSUES_LINUX": "****ПРЕДУПРЕЖДЕНИЕ:**** **Аппаратное ускорение** может потребоваться **отключить**. В некоторых случаях может появиться черный фон, например, когда окно обрезается сверху или снизу из-за разрешения монитора, или когда инструменты разработки открыты и пристыкованы.", + "REPLUGGED_SETTINGS_TRANSPARENT_ISSUES_WINDOWS": "Это приведет к нарушению **привязки окна**. В некоторых случаях может появиться черный фон, например, когда окно обрезается сверху или снизу из-за разрешения монитора, или когда инструменты разработки открыты и пристыкованы.", + "REPLUGGED_SETTINGS_TRANSPARENT_ISSUES_LINUX": "**Аппаратное ускорение** может потребоваться **отключить**. В некоторых случаях может появиться черный фон, например, когда окно обрезается сверху или снизу из-за разрешения монитора, или когда инструменты разработки открыты и пристыкованы.", "REPLUGGED_SETTINGS_TRANSPARENT_DESC": "Делает окно Discord прозрачным, полезно в создании тем. **Требуется перезапуск**.", "REPLUGGED_COMMAND_ERROR_GENERIC": "Что-то пошло не так, повторите попытку позже. Если проблема остается, обратитесь к команде Replugged.", "REPLUGGED_COMMAND_ENABLE_NAME": "включить", diff --git a/i18n/messages/tr.messages.json b/i18n/messages/tr.messages.json index 5095749f6..7748f2e34 100644 --- a/i18n/messages/tr.messages.json +++ b/i18n/messages/tr.messages.json @@ -209,8 +209,8 @@ "REPLUGGED_SETTINGS_ADDON_EMBEDS_DESC": "Sohbette bir eklentinin mağaza/indirme linki paylaşıldığında bu eklenti hakkında bir kart ile bilgi göster.", "REPLUGGED_SETTINGS_RESTART_TITLE": "Yeniden Başlatma Gerekli", "REPLUGGED_SETTINGS_TRANSPARENT": "Geçirgen Pencere", - "REPLUGGED_SETTINGS_TRANSPARENT_ISSUES_WINDOWS": "****DİKKAT:**** Bu **pencere tutturma**yı bozar. Bazı durumlarda siyah bir arkaplan görebilirsiniz, örneğin ekran çözünürlüğünden dolayı pencerenin üstten veya alttan kesildiği zamanlarda veya geliştirici araçları açık ve dok halinde olduğu zamanlarda.", - "REPLUGGED_SETTINGS_TRANSPARENT_ISSUES_LINUX": "****DİKKAT:**** **Donanım hızlandırma**yı **kapat**manız gerekebilir. Bazı durumlarda siyah bir arkaplan görebilirsiniz, örneğin ekran çözünürlüğünden dolayı pencerenin üstten veya alttan kesildiği zamanlarda veya geliştirici araçları açık ve dok halinde olduğu zamanlarda.", + "REPLUGGED_SETTINGS_TRANSPARENT_ISSUES_WINDOWS": "Bu **pencere tutturma**yı bozar. Bazı durumlarda siyah bir arkaplan görebilirsiniz, örneğin ekran çözünürlüğünden dolayı pencerenin üstten veya alttan kesildiği zamanlarda veya geliştirici araçları açık ve dok halinde olduğu zamanlarda.", + "REPLUGGED_SETTINGS_TRANSPARENT_ISSUES_LINUX": "**Donanım hızlandırma**yı **kapat**manız gerekebilir. Bazı durumlarda siyah bir arkaplan görebilirsiniz, örneğin ekran çözünürlüğünden dolayı pencerenin üstten veya alttan kesildiği zamanlarda veya geliştirici araçları açık ve dok halinde olduğu zamanlarda.", "REPLUGGED_SETTINGS_TRANSPARENT_DESC": "Discord penceresini geçirgen yapar, daha çok temalar içindir. **Yeniden başlatma gerektirir**.", "REPLUGGED_COMMAND_SUCCESS_GENERIC": "Başarılı", "REPLUGGED_COMMAND_LIST_OPTION_TYPE_CHOICE_PLUGIN": "Eklentileri Listele", diff --git a/i18n/messages/zh-TW.messages.json b/i18n/messages/zh-TW.messages.json index 5c85be622..ef217cbb4 100644 --- a/i18n/messages/zh-TW.messages.json +++ b/i18n/messages/zh-TW.messages.json @@ -211,7 +211,7 @@ "REPLUGGED_COMMAND_LIST_HEADER_ENABLED": "已啟用{type}", "REPLUGGED_COMMAND_LIST_HEADER_DISABLED": "已停用{type}", "REPLUGGED_COMMAND_LIST_ERROR_SPECIFY": "你需要指定發送插件列表或主題列表", - "REPLUGGED_SETTINGS_TRANSPARENT_ISSUES_WINDOWS": "****警告****:此操作會破壞**視窗對齊**的功能。在某些情況下,你可能會遇到黑屏的問題,例如當視窗由於螢幕解析度問題而被截斷在頂部或底部時,或者當開啟和停靠在開發工具時。", + "REPLUGGED_SETTINGS_TRANSPARENT_ISSUES_WINDOWS": "此操作會破壞**視窗對齊**的功能。在某些情況下,你可能會遇到黑屏的問題,例如當視窗由於螢幕解析度問題而被截斷在頂部或底部時,或者當開啟和停靠在開發工具時。", "REPLUGGED_SETTINGS_TRANSPARENT": "透明視窗", "REPLUGGED_SETTINGS_TRANSPARENT_DESC": "讓Discord視窗透明,主要用於主題。**需要重新啟動**。", "REPLUGGED_COMMAND_INSTALL_OPTION_ADDON_NAME": "附件元件", @@ -220,7 +220,7 @@ "REPLUGGED_COMMAND_INSTALL_OPTION_SOURCE_DESC": "安裝附加元件的來源", "REPLUGGED_COMMAND_INSTALL_OPTION_ID_NAME": "ID", "REPLUGGED_COMMAND_INSTALL_OPTION_ID_DESC": "如果來源有多個附加元件,請指定要安裝哪一個", - "REPLUGGED_SETTINGS_TRANSPARENT_ISSUES_LINUX": "****警告****:**硬體加速**可能需要**關閉**。在某些情況下,你可能會遇到黑屏的問題,例如窗口因螢幕解析度導致頂部或底部被切割掉,或者當開啟並停靠開發工具時。", + "REPLUGGED_SETTINGS_TRANSPARENT_ISSUES_LINUX": "**硬體加速**可能需要**關閉**。在某些情況下,你可能會遇到黑屏的問題,例如窗口因螢幕解析度導致頂部或底部被切割掉,或者當開啟並停靠開發工具時。", "REPLUGGED_STORE": "商店", "REPLUGGED_COMMAND_SUCCESS_GENERIC": "成功", "REPLUGGED_COMMAND_LIST_OPTION_TYPE_CHOICE_PLUGIN": "列出插件", diff --git a/src/main/index.ts b/src/main/index.ts index ef8ee1270..3da4fa188 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -31,11 +31,11 @@ Object.defineProperty(global, "appSettings", { class BrowserWindow extends electron.BrowserWindow { public constructor(opts: Electron.BrowserWindowConstructorOptions) { const titleBarSetting = getSetting("dev.replugged.Settings", "titleBar", false); - const transparentWindowSetting = getSetting( + const transparencySetting = getSetting<{ enabled: boolean }>( "dev.replugged.Settings", - "transparentWindow", - false, - ); + "transparency", + { enabled: false }, + ).enabled; if (opts.frame && process.platform === "linux" && titleBarSetting) opts.frame = void 0; @@ -48,73 +48,16 @@ class BrowserWindow extends electron.BrowserWindow { ) { opts.webPreferences.preload = join(__dirname, "./preload.js"); - if (transparentWindowSetting) { + if (transparencySetting) { if (process.platform === "win32" || process.platform === "linux") { opts.transparent = true; + opts.backgroundColor = "#00000000"; } } } super(opts); - // Center the unmaximized location - if (transparentWindowSetting) { - let lastBounds = this.getBounds(); - // Default to the center of the screen at 1440x810 scale for a 1080p monitor (75%) - const primaryDisplaySize = electron.screen.getPrimaryDisplay().workAreaSize; - let lastLastBounds = { - width: primaryDisplaySize.width * 0.75, - height: primaryDisplaySize.height * 0.75, - x: primaryDisplaySize.width / 2 - (primaryDisplaySize.width * 0.75) / 2, - y: primaryDisplaySize.height / 2 - (primaryDisplaySize.height * 0.75) / 2, - }; - let lastResize = Date.now(); - this.on("resize", () => { - const bounds = this.getBounds(); - lastLastBounds = lastBounds; - lastBounds = bounds; - lastResize = Date.now(); - }); - - this.on("maximize", () => { - // Get the display at the center of the window - const screenBounds = this.getBounds(); - const windowDisplay = electron.screen.getDisplayNearestPoint({ - x: screenBounds.x + screenBounds.width / 2, - y: screenBounds.y + screenBounds.height / 2, - }); - const workAreaSize = windowDisplay.workArea; - - const isSizeMaximized = - lastBounds.width === workAreaSize.width && lastBounds.height === workAreaSize.height; - const isPositionMaximized = - lastBounds.x === workAreaSize.x + 1 && lastBounds.y === workAreaSize.y + 1; - - // if we haven't resized in the last few ms, we probably didn't actually maximize and should instead unmaximize - if (lastResize < Date.now() - 10 || (isSizeMaximized && isPositionMaximized)) { - // Calculate new x, y to be in the center of the monitor - this.setBounds({ - x: workAreaSize.width / 2 - lastLastBounds.width / 2 + workAreaSize.x, - y: workAreaSize.height / 2 - lastLastBounds.height / 2 + workAreaSize.y, - width: lastLastBounds.width, - height: lastLastBounds.height, - }); - - lastResize = Date.now(); - return; - } - - // Move the window to 1,1 to mitigate the window going grey when maximized - // Note that the window doesn't seem to visually be at 1,1, but that's enough to prevent the greying - this.setBounds({ - x: workAreaSize.x + 1, - y: workAreaSize.y + 1, - width: screenBounds.width, - height: screenBounds.height, - }); - }); - } - (this.webContents as RepluggedWebContents).originalPreload = originalPreload; } } diff --git a/src/main/ipc/transparency.ts b/src/main/ipc/transparency.ts index d16f3d823..5a50e4332 100644 --- a/src/main/ipc/transparency.ts +++ b/src/main/ipc/transparency.ts @@ -1,8 +1,6 @@ import { BrowserWindow, ipcMain } from "electron"; import { type BackgroundMaterialType, RepluggedIpcChannels, type VibrancyType } from "src/types"; -const DEFAULT_BACKGROUND_COLOR = "#00000000"; - let backgroundMaterial: BackgroundMaterialType | null = null; ipcMain.handle(RepluggedIpcChannels.GET_BACKGROUND_MATERIAL, (): BackgroundMaterialType | null => { if (process.platform !== "win32") { @@ -37,23 +35,3 @@ ipcMain.handle(RepluggedIpcChannels.SET_VIBRANCY, (_, vibrancy: VibrancyType) => windows.forEach((window) => window.setVibrancy(vibrancy)); currentVibrancy = vibrancy; }); - -let currentBackgroundColor = DEFAULT_BACKGROUND_COLOR; -ipcMain.handle(RepluggedIpcChannels.GET_BACKGROUND_COLOR, () => { - if (process.platform !== "win32") { - console.warn("GET_BACKGROUND_COLOR only works on Windows"); - } - - return currentBackgroundColor; -}); - -ipcMain.handle(RepluggedIpcChannels.SET_BACKGROUND_COLOR, (_, color: string | undefined) => { - if (process.platform !== "win32") { - console.warn("SET_BACKGROUND_COLOR only works on Windows"); - return; - } - - const windows = BrowserWindow.getAllWindows(); - windows.forEach((window) => window.setBackgroundColor(color || DEFAULT_BACKGROUND_COLOR)); - currentBackgroundColor = color || DEFAULT_BACKGROUND_COLOR; -}); diff --git a/src/renderer/coremods/installer/util.tsx b/src/renderer/coremods/installer/util.tsx index 30bfbd368..125790082 100644 --- a/src/renderer/coremods/installer/util.tsx +++ b/src/renderer/coremods/installer/util.tsx @@ -85,7 +85,7 @@ export async function getInfo( return cached.data; } - const info = await RepluggedNative.installer.getInfo(source, identifier, id); + const info = await window.RepluggedNative.installer.getInfo(source, identifier, id); if (!info.success) { logger.error(`Failed to get info for ${identifier}: ${info.error}`); cache.set(cacheIdentifier, { @@ -154,7 +154,7 @@ export async function install(data: CheckResultSuccess): Promise { manifest: { name, type, id, version }, } = data; - const res = await RepluggedNative.installer.install( + const res = await window.RepluggedNative.installer.install( type, `${id}.asar`, url, diff --git a/src/renderer/coremods/settings/pages/General.tsx b/src/renderer/coremods/settings/pages/General.tsx index b47c40d64..bd8644b9b 100644 --- a/src/renderer/coremods/settings/pages/General.tsx +++ b/src/renderer/coremods/settings/pages/General.tsx @@ -3,7 +3,6 @@ import { t as discordT, intl } from "@common/i18n"; import { Button, ButtonItem, - Category, Divider, FormItem, Notice, @@ -17,13 +16,8 @@ import { WEBSITE_URL } from "src/constants"; import { generalSettings } from "src/renderer/managers/settings"; import { t } from "src/renderer/modules/i18n"; import * as util from "src/renderer/util"; -import type { BackgroundMaterialType, VibrancyType } from "src/types"; +import { BACKGROUND_MATERIALS, VIBRANCY_VALUES } from "src/types"; import { initWs, socket } from "../../devCompanion"; -import { - updateBackgroundColor, - updateBackgroundMaterial, - updateVibrancy, -} from "../../transparency"; import "./General.css"; @@ -70,36 +64,22 @@ const GeneralSettingsTabs = { GENERAL: "general", ADVANCED: "advanced" } as cons function GeneralTab(): React.ReactElement { const [quickCSSValue, quickCSSOnChange] = util.useSettingArray(generalSettings, "quickCSS"); const [titleBarValue, titleBarOnChange] = util.useSettingArray(generalSettings, "titleBar"); - const [transValue, transOnChange] = util.useSettingArray(generalSettings, "transparency.enabled"); - const [overrideBgColValue, overrideBgColOnChange] = util.useSettingArray( - generalSettings, - "transparency.overrideWindowBackgroundColor", - ); - const [bgColValue] = util.useSettingArray(generalSettings, "transparency.windowBackgroundColor"); - const [overrideBgMatValue, overrideBgMatOnChange] = util.useSettingArray( - generalSettings, - "transparency.overrideWindowBackgroundMaterial", - ); - const [bgMatValue, bgMatOnChange] = util.useSettingArray( + const [transparencyValue, transparencyOnChange] = util.useSettingArray( generalSettings, - "transparency.windowBackgroundMaterial", + "transparency.enabled", + false, ); - const [overrideVibrancyValue, overrideVibrancyOnChange] = util.useSettingArray( + const [backgroundMaterialValue, backgroundMaterialOnChange] = util.useSettingArray( generalSettings, - "transparency.overrideWindowVibrancy", + "transparency.backgroundMaterial", + "none", ); const [vibrancyValue, vibrancyOnChange] = util.useSettingArray( generalSettings, - "transparency.windowVibrancy", + "transparency.vibrancy", + "content", ); - React.useEffect(() => { - if (quickCSSValue) window.replugged.quickCSS.load(); - else window.replugged.quickCSS.unload(); - }, [quickCSSValue]); - - const isLinux = DiscordNative.process.platform === "linux"; - return ( <> + className={classNames({ + [marginStyles.marginBottom40]: window.DiscordNative.process.platform !== "linux", + })}> {intl.string(t.REPLUGGED_SETTINGS_ADDON_EMBEDS)} - {isLinux && ( + {window.DiscordNative.process.platform === "linux" && ( { @@ -125,10 +107,14 @@ function GeneralTab(): React.ReactElement { {intl.string(t.REPLUGGED_SETTINGS_CUSTOM_TITLE_BAR)} )} - + { + quickCSSOnChange(value); + if (value) window.replugged.quickCSS.load(); + else window.replugged.quickCSS.unload(); + }} note={intl.string(t.REPLUGGED_SETTINGS_QUICKCSS_ENABLE_DESC)}> {intl.string(t.REPLUGGED_SETTINGS_QUICKCSS_ENABLE)} @@ -139,233 +125,60 @@ function GeneralTab(): React.ReactElement { {intl.string(t.REPLUGGED_SETTINGS_QUICKCSS_AUTO_APPLY)} - - note="Window transparency settings"> -
- {(DiscordNative.process.platform === "linux" || - DiscordNative.process.platform === "win32") && ( - - {DiscordNative.process.platform === "linux" - ? intl.format(t.REPLUGGED_SETTINGS_TRANSPARENT_ISSUES_LINUX, {}) - : intl.format(t.REPLUGGED_SETTINGS_TRANSPARENT_ISSUES_WINDOWS, {})} - - )} -
+ + {(window.DiscordNative.process.platform === "linux" || + window.DiscordNative.process.platform === "win32") && ( + + {window.DiscordNative.process.platform === "linux" + ? intl.format(t.REPLUGGED_SETTINGS_TRANSPARENT_ISSUES_LINUX, {}) + : intl.format(t.REPLUGGED_SETTINGS_TRANSPARENT_ISSUES_WINDOWS, {})} + + )} { - transOnChange(value); + transparencyOnChange(value); restartModal(true); }} note={intl.format(t.REPLUGGED_SETTINGS_TRANSPARENT_DESC, {})}> {intl.string(t.REPLUGGED_SETTINGS_TRANSPARENT)} - - {DiscordNative.process.platform === "win32" && ( - <> - { - overrideBgColOnChange(value); - - if (value) { - void RepluggedNative.transparency.setBackgroundColor(bgColValue); - } else { - void updateBackgroundColor(); - } - }} - disabled={!transValue}> - {/* {intl.string(t.REPLUGGED_SETTINGS_OVERRIDE_BG_COLOR)} */} - Override Window Background Color - - - {/* { - const newValue = `#${value.toString(16).padStart(6, "0").padEnd(8, "0")}`; - bgColOnChange(newValue); - void RepluggedNative.transparency.setBackgroundColor(newValue); - }} - disabled={!generalSettings.get("overrideWindowBackgroundColor") || !transValue} - customPickerPosition="right" - renderCustomButton={React.useCallback((props: object) => { - const button = ; - // return disabled ? button : - return button; - }, [])} - renderDefaultButton={React.useCallback( - (props: object) => ( - // < - - ), - [], - )} - /> */} - - - { - overrideBgMatOnChange(value); - if (value) { - void RepluggedNative.transparency.setBackgroundMaterial( - bgMatValue as BackgroundMaterialType, - ); - } else { - // If this line is uncommented, coremods kinda die - void updateBackgroundMaterial(); - } - }} - disabled={!transValue}> - {/* {intl.string(t.REPLUGGED_SETTINGS_OVERRIDE_BG_MATERIAL)} */} - Override Window Background Material - - { - bgMatOnChange(value); - void RepluggedNative.transparency.setBackgroundMaterial( - value as BackgroundMaterialType, - ); - }} - disabled={!overrideBgMatValue || !transValue} - options={[ - { - label: "None", - value: "none", - }, - { - label: "Acrylic", - value: "acrylic", - }, - { - label: "Mica", - value: "mica", - }, - { - label: "Tabbed", - value: "tabbed", - }, - ]}> - Background Material - - + {window.DiscordNative.process.platform === "win32" && ( + { + backgroundMaterialOnChange(value); + void window.RepluggedNative.transparency.setBackgroundMaterial(value); + }} + disabled={!transparencyValue} + options={BACKGROUND_MATERIALS.map((m) => ({ + label: m.charAt(0).toUpperCase() + m.slice(1), + value: m, + }))}> + {intl.string(t.REPLUGGED_SETTINGS_TRANSPARENCY_BG_MATERIAL)} + )} - - {DiscordNative.process.platform === "darwin" && ( - <> - {/* @todo: This should trigger the SelectItem's onChange with it's current value to apply the overridden material */} - { - overrideVibrancyOnChange(overrideVibrancyValue); - if (value) { - void RepluggedNative.transparency.setVibrancy( - vibrancyValue as - | "titlebar" - | "selection" - | "menu" - | "popover" - | "sidebar" - | "header" - | "sheet" - | "window" - | "hud" - | "fullscreen-ui" - | "tooltip" - | "content" - | "under-window" - | "under-page" - | null, - ); - } else { - void updateVibrancy(); - } - }} - disabled={!transValue}> - {/* {intl.string(t.REPLUGGED_SETTINGS_OVERRIDE_VIBRANCY)} */} - Override Window Vibrancy - - { - vibrancyOnChange(value); - void RepluggedNative.transparency.setVibrancy(value as VibrancyType); - }} - disabled={!overrideVibrancyValue || !transValue} - options={[ - { - label: "Titlebar", - value: "titlebar", - }, - { - label: "Selection", - value: "selection", - }, - { - label: "Menu", - value: "menu", - }, - { - label: "Popover", - value: "popover", - }, - { - label: "Sidebar", - value: "sidebar", - }, - { - label: "Header", - value: "header", - }, - { - label: "Sheet", - value: "sheet", - }, - { - label: "Window", - value: "window", - }, - { - label: "HUD", - value: "hud", - }, - { - label: "Fullscreen UI", - value: "fullscreen-ui", - }, - { - label: "Tooltip", - value: "tooltip", - }, - { - label: "Content", - value: "content", - }, - { - label: "Under Window", - value: "under-window", - }, - { - label: "Under Page", - value: "under-page", - }, - ]}> - Vibrancy - - + {window.DiscordNative.process.platform === "darwin" && ( + { + vibrancyOnChange(value); + void window.RepluggedNative.transparency.setVibrancy(value); + }} + disabled={!transparencyValue} + options={VIBRANCY_VALUES.map((v) => ({ + label: v + .split(/[-_]/) + .map((s) => s.charAt(0).toUpperCase() + s.slice(1)) + .join(" "), + value: v, + }))}> + {intl.string(t.REPLUGGED_SETTINGS_TRANSPARENCY_VIBRANCY)} + )} -
+
); } @@ -419,9 +232,9 @@ function AdvancedTab(): React.ReactElement { try { rdtOnChange(value); if (value) { - await RepluggedNative.reactDevTools.downloadExtension(); + await window.RepluggedNative.reactDevTools.downloadExtension(); } else { - await RepluggedNative.reactDevTools.removeExtension(); + await window.RepluggedNative.reactDevTools.removeExtension(); } restartModal(true); } catch { @@ -429,7 +242,7 @@ function AdvancedTab(): React.ReactElement { rdtOnChange(false); if (value) { try { - await RepluggedNative.reactDevTools.removeExtension(); + await window.RepluggedNative.reactDevTools.removeExtension(); } catch { // Ignore cleanup errors } diff --git a/src/renderer/coremods/transparency/index.ts b/src/renderer/coremods/transparency/index.ts deleted file mode 100644 index 93bcf07e6..000000000 --- a/src/renderer/coremods/transparency/index.ts +++ /dev/null @@ -1,105 +0,0 @@ -import { Logger } from "@replugged"; -import { generalSettings } from "src/renderer/managers/settings"; - -let observer: MutationObserver; - -function getRootProperty(property: string): string { - const computedStyle = getComputedStyle(document.body); - const value = computedStyle.getPropertyValue(property); - - return value; -} - -function getRootStringProperty(property: string): string { - const value = getRootProperty(property); - return value.split('"')[1]; -} - -const logger = Logger.coremod("Transparency"); - -export async function updateBackgroundMaterial(): Promise { - if (generalSettings.get("transparency").overrideWindowBackgroundMaterial) return; - - const backgroundMaterial = getRootStringProperty("--window-background-material"); - if (backgroundMaterial !== (await RepluggedNative.transparency.getBackgroundMaterial())) { - logger.log("Setting background material to:", backgroundMaterial); - // @ts-expect-error @todo: Check if the transparency effect is valid? - await RepluggedNative.transparency.setBackgroundMaterial(backgroundMaterial); - } -} - -export async function updateBackgroundColor(): Promise { - if (generalSettings.get("transparency").overrideWindowBackgroundColor) return; - - const backgroundColor = getRootProperty("--window-background-color"); - if (backgroundColor !== (await RepluggedNative.transparency.getBackgroundColor())) { - logger.log("Setting background color to:", backgroundColor); - await RepluggedNative.transparency.setBackgroundColor(backgroundColor); - } -} - -export async function updateVibrancy(): Promise { - if (generalSettings.get("transparency").overrideWindowBackgroundMaterial) return; - - const vibrancy = getRootStringProperty("--window-vibrancy"); - if (vibrancy !== (await RepluggedNative.transparency.getVibrancy())) { - logger.log("Setting vibrancy effect to:", vibrancy); - // @ts-expect-error @todo: Check if the vibrancy is valid? - await RepluggedNative.transparency.setVibrancy(vibrancy); - } -} - -export function start(): void { - const html = document.body.parentElement!; - - observer = new MutationObserver((mutations) => { - let cssModified = false; - for (const mutation of mutations) { - if (mutation.target instanceof HTMLLinkElement && mutation.type === "attributes") { - cssModified = true; - break; - } - - if (mutation.type !== "childList") continue; - - // Check for both added or removed css(like) nodes - const changedNodes = Array.from(mutation.addedNodes).concat( - Array.from(mutation.removedNodes), - ); - const cssLikeNodes = changedNodes.filter( - (node) => node instanceof HTMLStyleElement || node instanceof HTMLLinkElement, - ); - if (cssLikeNodes.length > 0) { - cssModified = true; - break; - } - } - - if (cssModified) { - setTimeout(async () => { - switch (DiscordNative.process.platform) { - case "win32": { - await Promise.all([updateBackgroundMaterial(), updateBackgroundColor()]); - break; - } - case "darwin": { - await updateVibrancy(); - break; - } - } - }, 100); - } - }); - - observer.observe(html, { - subtree: true, - childList: true, - // To handle any instances where a link has it's href changed. - attributes: true, - attributeFilter: ["href"], - }); -} - -export function stop(): void { - observer.disconnect(); -} diff --git a/src/renderer/managers/coremods.ts b/src/renderer/managers/coremods.ts index 8428664ac..9326dbb4a 100644 --- a/src/renderer/managers/coremods.ts +++ b/src/renderer/managers/coremods.ts @@ -41,7 +41,6 @@ export namespace coremods { export let rpc: Coremod; export let settings: Coremod; export let themeUtils: Coremod; - export let transparency: Coremod; export let watcher: Coremod; export let welcome: Coremod; } @@ -69,7 +68,6 @@ export async function startAll(): Promise { coremods.rpc = await import("../coremods/rpc"); coremods.settings = await import("../coremods/settings"); coremods.themeUtils = await import("../coremods/themeUtils"); - coremods.transparency = await import("../coremods/transparency"); coremods.watcher = await import("../coremods/watcher"); coremods.welcome = await import("../coremods/welcome"); diff --git a/src/renderer/managers/plugins.ts b/src/renderer/managers/plugins.ts index d45517a52..b0a3af63f 100644 --- a/src/renderer/managers/plugins.ts +++ b/src/renderer/managers/plugins.ts @@ -171,7 +171,7 @@ export function runPlaintextPatches(): void { // This is a bit of a hack, plaintext patches are built in ESM, but we need to run it in a CJS context try { - const code = RepluggedNative.plugins.getPlaintextPatches(plugin.path); + const code = window.RepluggedNative.plugins.getPlaintextPatches(plugin.path); let patches: { default: PlaintextPatch[] } = { default: [] }; if (code) { diff --git a/src/renderer/managers/settings.ts b/src/renderer/managers/settings.ts index 3a6cafec6..c47265f62 100644 --- a/src/renderer/managers/settings.ts +++ b/src/renderer/managers/settings.ts @@ -17,12 +17,8 @@ export type GeneralSettings = { keepToken?: boolean; transparency?: { enabled?: boolean; - overrideWindowBackgroundColor?: boolean; - windowBackgroundColor?: string; - overrideWindowBackgroundMaterial?: boolean; - windowBackgroundMaterial?: BackgroundMaterialType; - overrideWindowVibrancy?: boolean; - windowVibrancy?: VibrancyType; + backgroundMaterial?: BackgroundMaterialType; + vibrancy?: VibrancyType; }; }; @@ -40,12 +36,8 @@ const defaultSettings = { keepToken: false, transparency: { enabled: false, - overrideWindowBackgroundColor: false, - windowBackgroundColor: "#00000000", - overrideWindowBackgroundMaterial: false, - windowBackgroundMaterial: "none", - overrideWindowVibrancy: false, - windowVibrancy: "content", + backgroundMaterial: "none", + vibrancy: "content", }, } satisfies Partial; diff --git a/src/types/coremods/transparency.ts b/src/types/coremods/transparency.ts index 02b77a1ba..32c02aa22 100644 --- a/src/types/coremods/transparency.ts +++ b/src/types/coremods/transparency.ts @@ -1,7 +1,20 @@ -import { BrowserWindow } from "electron"; +export const VIBRANCY_VALUES = [ + "titlebar", + "selection", + "menu", + "popover", + "sidebar", + "header", + "sheet", + "window", + "hud", + "fullscreen-ui", + "tooltip", + "content", + "under-window", + "under-page", +] as const; +export type VibrancyType = (typeof VIBRANCY_VALUES)[number]; -export type BackgroundMaterialType = Parameters< - typeof BrowserWindow.prototype.setBackgroundMaterial ->[0]; - -export type VibrancyType = Parameters[0]; +export const BACKGROUND_MATERIALS = ["auto", "none", "mica", "acrylic", "tabbed"] as const; +export type BackgroundMaterialType = (typeof BACKGROUND_MATERIALS)[number]; From 41366800e80c64a5a53e62c3adf2eb772f95a35b Mon Sep 17 00:00:00 2001 From: Federico <38290480+fedeericodl@users.noreply.github.com> Date: Sat, 4 Oct 2025 19:22:53 +0200 Subject: [PATCH 43/47] feat: refactor transparency settings --- i18n/en-US.messages.d.ts | 52 +++++------ i18n/en-US.messages.js | 4 +- i18n/messages/en-US.messages.json | 4 +- src/main/index.ts | 30 +++--- src/main/ipc/settings.ts | 8 +- src/main/ipc/transparency.ts | 2 +- src/preload.ts | 9 +- .../coremods/settings/pages/General.tsx | 91 +++++++++---------- src/renderer/managers/settings.ts | 16 ++-- src/types/coremods/transparency.ts | 32 +++---- src/types/index.ts | 2 - 11 files changed, 116 insertions(+), 134 deletions(-) diff --git a/i18n/en-US.messages.d.ts b/i18n/en-US.messages.d.ts index 2e23b2510..7ba7d5a8e 100644 --- a/i18n/en-US.messages.d.ts +++ b/i18n/en-US.messages.d.ts @@ -1737,19 +1737,6 @@ export declare const messages: { * Missing translations: `bg`, `da`, `el`, `es-419`, `hi`, `hr`, `hu`, `ko`, `lt`, `nl`, `no`, `ro`, `sv-SE`, `th`, `zh-CN` */ 'REPLUGGED_SETTINGS_RESTART_TITLE': TypedIntlMessageGetter<{}>, - /** - * Key: `ujAx5O` - * - * ### Definition - * ```text - * Transparency - * ``` - * - * ### Problems - * - * Missing translations: `bg`, `cs`, `da`, `de`, `el`, `en-GB`, `es-419`, `es-ES`, `fi`, `fr`, `hi`, `hr`, `hu`, `it`, `ja`, `ko`, `lt`, `nl`, `no`, `pl`, `pt-BR`, `ro`, `ru`, `sv-SE`, `th`, `tr`, `uk`, `vi`, `zh-CN`, `zh-TW` - */ - 'REPLUGGED_SETTINGS_TRANSPARENCY': TypedIntlMessageGetter<{}>, /** * Key: `e6ACBQ` * @@ -1763,19 +1750,6 @@ export declare const messages: { * Missing translations: `bg`, `cs`, `da`, `de`, `el`, `en-GB`, `es-419`, `es-ES`, `fi`, `fr`, `hi`, `hr`, `hu`, `it`, `ja`, `ko`, `lt`, `nl`, `no`, `pl`, `pt-BR`, `ro`, `ru`, `sv-SE`, `th`, `tr`, `uk`, `vi`, `zh-CN`, `zh-TW` */ 'REPLUGGED_SETTINGS_TRANSPARENCY_BG_MATERIAL': TypedIntlMessageGetter<{}>, - /** - * Key: `eCCNpa` - * - * ### Definition - * ```text - * Manage transparency and visual effects for the Discord window. - * ``` - * - * ### Problems - * - * Missing translations: `bg`, `cs`, `da`, `de`, `el`, `en-GB`, `es-419`, `es-ES`, `fi`, `fr`, `hi`, `hr`, `hu`, `it`, `ja`, `ko`, `lt`, `nl`, `no`, `pl`, `pt-BR`, `ro`, `ru`, `sv-SE`, `th`, `tr`, `uk`, `vi`, `zh-CN`, `zh-TW` - */ - 'REPLUGGED_SETTINGS_TRANSPARENCY_DESC': TypedIntlMessageGetter<{}>, /** * Key: `sO01jI` * @@ -1841,6 +1815,32 @@ export declare const messages: { * Missing translations: `bg`, `da`, `el`, `es-419`, `es-ES`, `fr`, `hi`, `hr`, `hu`, `ko`, `lt`, `nl`, `no`, `ro`, `sv-SE`, `th`, `uk`, `vi`, `zh-CN` */ 'REPLUGGED_SETTINGS_TRANSPARENT_ISSUES_WINDOWS': TypedIntlMessageGetter<{$b?: HookFunction}>, + /** + * Key: `J3A61d` + * + * ### Definition + * ```text + * Window + * ``` + * + * ### Problems + * + * Missing translations: `bg`, `cs`, `da`, `de`, `el`, `en-GB`, `es-419`, `es-ES`, `fi`, `fr`, `hi`, `hr`, `hu`, `it`, `ja`, `ko`, `lt`, `nl`, `no`, `pl`, `pt-BR`, `ro`, `ru`, `sv-SE`, `th`, `tr`, `uk`, `vi`, `zh-CN`, `zh-TW` + */ + 'REPLUGGED_SETTINGS_WINDOW': TypedIntlMessageGetter<{}>, + /** + * Key: `XuDbXF` + * + * ### Definition + * ```text + * Manage the Discord window appearance and behavior. + * ``` + * + * ### Problems + * + * Missing translations: `bg`, `cs`, `da`, `de`, `el`, `en-GB`, `es-419`, `es-ES`, `fi`, `fr`, `hi`, `hr`, `hu`, `it`, `ja`, `ko`, `lt`, `nl`, `no`, `pl`, `pt-BR`, `ro`, `ru`, `sv-SE`, `th`, `tr`, `uk`, `vi`, `zh-CN`, `zh-TW` + */ + 'REPLUGGED_SETTINGS_WINDOW_DESC': TypedIntlMessageGetter<{}>, /** * Key: `b2oqX1` * diff --git a/i18n/en-US.messages.js b/i18n/en-US.messages.js index a62e0b4b1..5c45c929e 100644 --- a/i18n/en-US.messages.js +++ b/i18n/en-US.messages.js @@ -242,8 +242,8 @@ export default defineMessages({ "REPLUGGED_ADDON_SETTINGS_THEME_PRESET": "Choose Theme Preset", "REPLUGGED_TOAST_THEME_PRESET_CHANGED": "Switched to preset: {name}", "REPLUGGED_TOAST_THEME_PRESET_FAILED": "Failed to change preset for {name}", - "REPLUGGED_SETTINGS_TRANSPARENCY": "Transparency", - "REPLUGGED_SETTINGS_TRANSPARENCY_DESC": "Manage transparency and visual effects for the Discord window.", + "REPLUGGED_SETTINGS_WINDOW": "Window", + "REPLUGGED_SETTINGS_WINDOW_DESC": "Manage the Discord window appearance and behavior.", "REPLUGGED_SETTINGS_TRANSPARENCY_BG_MATERIAL": "Background Material", "REPLUGGED_SETTINGS_TRANSPARENCY_VIBRANCY": "Vibrancy" }); \ No newline at end of file diff --git a/i18n/messages/en-US.messages.json b/i18n/messages/en-US.messages.json index f1d134003..3defc6b34 100644 --- a/i18n/messages/en-US.messages.json +++ b/i18n/messages/en-US.messages.json @@ -237,8 +237,8 @@ "REPLUGGED_ADDON_SETTINGS_THEME_PRESET": "Choose Theme Preset", "REPLUGGED_TOAST_THEME_PRESET_CHANGED": "Switched to preset: {name}", "REPLUGGED_TOAST_THEME_PRESET_FAILED": "Failed to change preset for {name}", - "REPLUGGED_SETTINGS_TRANSPARENCY": "Transparency", - "REPLUGGED_SETTINGS_TRANSPARENCY_DESC": "Manage transparency and visual effects for the Discord window.", + "REPLUGGED_SETTINGS_WINDOW": "Window", + "REPLUGGED_SETTINGS_WINDOW_DESC": "Manage the Discord window appearance and behavior.", "REPLUGGED_SETTINGS_TRANSPARENCY_BG_MATERIAL": "Background Material", "REPLUGGED_SETTINGS_TRANSPARENCY_VIBRANCY": "Vibrancy" } diff --git a/src/main/index.ts b/src/main/index.ts index 3da4fa188..9ca8fd02a 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -3,9 +3,9 @@ import { dirname, join } from "path"; import { CONFIG_PATHS } from "src/util.mjs"; import type { PackageJson } from "type-fest"; import { pathToFileURL } from "url"; -import type { RepluggedWebContents } from "../types"; +import type { BackgroundMaterialType, RepluggedWebContents, VibrancyType } from "../types"; import { getAddonInfo, getRepluggedVersion, installAddon } from "./ipc/installer"; -import { getSetting } from "./ipc/settings"; +import { getAllSettings, getSetting } from "./ipc/settings"; const electronPath = require.resolve("electron"); const discordPath = join(dirname(require.main!.filename), "..", "app.orig.asar"); @@ -30,17 +30,11 @@ Object.defineProperty(global, "appSettings", { // Thank you, Ven, for pointing this out! class BrowserWindow extends electron.BrowserWindow { public constructor(opts: Electron.BrowserWindowConstructorOptions) { - const titleBarSetting = getSetting("dev.replugged.Settings", "titleBar", false); - const transparencySetting = getSetting<{ enabled: boolean }>( - "dev.replugged.Settings", - "transparency", - { enabled: false }, - ).enabled; - - if (opts.frame && process.platform === "linux" && titleBarSetting) opts.frame = void 0; - + const generalSettings = getAllSettings("dev.replugged.Settings"); const originalPreload = opts.webPreferences?.preload; + if (opts.frame && process.platform === "linux" && generalSettings.titleBar) opts.frame = void 0; + // Load our preload script if it's the main window or the splash screen if ( opts.webPreferences?.preload && @@ -48,16 +42,20 @@ class BrowserWindow extends electron.BrowserWindow { ) { opts.webPreferences.preload = join(__dirname, "./preload.js"); - if (transparencySetting) { - if (process.platform === "win32" || process.platform === "linux") { - opts.transparent = true; - opts.backgroundColor = "#00000000"; + if (generalSettings.transparency) { + opts.transparent = true; + opts.backgroundColor = "#00000000"; + if (process.platform === "win32" && generalSettings.backgroundMaterial) { + opts.backgroundMaterial = generalSettings.backgroundMaterial as BackgroundMaterialType; } } + + if (process.platform === "darwin" && generalSettings.vibrancy) { + opts.vibrancy = generalSettings.vibrancy as VibrancyType; + } } super(opts); - (this.webContents as RepluggedWebContents).originalPreload = originalPreload; } } diff --git a/src/main/ipc/settings.ts b/src/main/ipc/settings.ts index f2a56a366..28e839219 100644 --- a/src/main/ipc/settings.ts +++ b/src/main/ipc/settings.ts @@ -115,10 +115,12 @@ ipcMain.on(RepluggedIpcChannels.DELETE_SETTING, (event, namespace: string, key: event.returnValue = writeTransaction(namespace, (settings) => settings.delete(key)); }); +export function getAllSettings(namespace: string): Record { + return readTransaction(namespace, (settings) => Object.fromEntries(settings.entries())); +} + ipcMain.on(RepluggedIpcChannels.GET_ALL_SETTINGS, (event, namespace: string) => { - event.returnValue = readTransaction(namespace, (settings) => - Object.fromEntries(settings.entries()), - ); + event.returnValue = getAllSettings(namespace); }); ipcMain.on(RepluggedIpcChannels.OPEN_SETTINGS_FOLDER, () => shell.openPath(SETTINGS_DIR)); diff --git a/src/main/ipc/transparency.ts b/src/main/ipc/transparency.ts index 5a50e4332..13b1e9660 100644 --- a/src/main/ipc/transparency.ts +++ b/src/main/ipc/transparency.ts @@ -29,7 +29,7 @@ ipcMain.handle( let currentVibrancy: VibrancyType | null = null; ipcMain.handle(RepluggedIpcChannels.GET_VIBRANCY, (): VibrancyType | null => currentVibrancy); -ipcMain.handle(RepluggedIpcChannels.SET_VIBRANCY, (_, vibrancy: VibrancyType) => { +ipcMain.handle(RepluggedIpcChannels.SET_VIBRANCY, (_, vibrancy: VibrancyType | null) => { const windows = BrowserWindow.getAllWindows(); windows.forEach((window) => window.setVibrancy(vibrancy)); diff --git a/src/preload.ts b/src/preload.ts index ebd02cb0e..71cf82c52 100644 --- a/src/preload.ts +++ b/src/preload.ts @@ -100,12 +100,9 @@ const RepluggedNative = { ipcRenderer.invoke(RepluggedIpcChannels.GET_BACKGROUND_MATERIAL), setBackgroundMaterial: (effect: BackgroundMaterialType): Promise => ipcRenderer.invoke(RepluggedIpcChannels.SET_BACKGROUND_MATERIAL, effect), - getBackgroundColor: (): Promise => - ipcRenderer.invoke(RepluggedIpcChannels.GET_BACKGROUND_COLOR), - setBackgroundColor: (color: string): Promise => - ipcRenderer.invoke(RepluggedIpcChannels.SET_BACKGROUND_COLOR, color), - getVibrancy: (): Promise => ipcRenderer.invoke(RepluggedIpcChannels.GET_VIBRANCY), - setVibrancy: (vibrancy: VibrancyType): Promise => + getVibrancy: (): Promise => + ipcRenderer.invoke(RepluggedIpcChannels.GET_VIBRANCY), + setVibrancy: (vibrancy: VibrancyType | null): Promise => ipcRenderer.invoke(RepluggedIpcChannels.SET_VIBRANCY, vibrancy), // visualEffectState does not need to be implemented until https://github.com/electron/electron/issues/25513 is implemented. }, diff --git a/src/renderer/coremods/settings/pages/General.tsx b/src/renderer/coremods/settings/pages/General.tsx index 6ac5ee38c..c2195386b 100644 --- a/src/renderer/coremods/settings/pages/General.tsx +++ b/src/renderer/coremods/settings/pages/General.tsx @@ -18,7 +18,7 @@ import * as QuickCSS from "src/renderer/managers/quick-css"; import { generalSettings } from "src/renderer/managers/settings"; import { t } from "src/renderer/modules/i18n"; import { useSetting, useSettingArray } from "src/renderer/util"; -import { BACKGROUND_MATERIALS, VIBRANCY_VALUES } from "src/types"; +import { BACKGROUND_MATERIALS, VIBRANCY_SELECT_OPTIONS } from "src/types"; import { initWs, socket } from "../../devCompanion"; import "./General.css"; @@ -72,21 +72,12 @@ function GeneralTab(): React.ReactElement { generalSettings, "autoApplyQuickCss", ); - const [transparency, setTransparency] = useSettingArray( - generalSettings, - "transparency.enabled", - false, - ); + const [transparency, setTransparency] = useSettingArray(generalSettings, "transparency"); const [backgroundMaterial, setBackgroundMaterial] = useSettingArray( generalSettings, - "transparency.backgroundMaterial", - "none", - ); - const [vibrancy, setVibrancy] = useSettingArray( - generalSettings, - "transparency.vibrancy", - "content", + "backgroundMaterial", ); + const [vibrancy, setVibrancy] = useSettingArray(generalSettings, "vibrancy"); return ( @@ -103,17 +94,6 @@ function GeneralTab(): React.ReactElement { label={intl.string(t.REPLUGGED_SETTINGS_ADDON_EMBEDS)} description={intl.string(t.REPLUGGED_SETTINGS_ADDON_EMBEDS_DESC)} /> - {window.DiscordNative.process.platform === "linux" && ( - { - setTitleBar(value); - restartModal(true); - }} - label={intl.string(t.REPLUGGED_SETTINGS_CUSTOM_TITLE_BAR)} - description={intl.format(t.REPLUGGED_SETTINGS_CUSTOM_TITLE_BAR_DESC, {})} - /> - )}
@@ -137,25 +117,42 @@ function GeneralTab(): React.ReactElement {
- {(window.DiscordNative.process.platform === "linux" || - window.DiscordNative.process.platform === "win32") && ( - - {window.DiscordNative.process.platform === "linux" - ? intl.format(t.REPLUGGED_SETTINGS_TRANSPARENT_ISSUES_LINUX, {}) - : intl.format(t.REPLUGGED_SETTINGS_TRANSPARENT_ISSUES_WINDOWS, {})} - + label={intl.string(t.REPLUGGED_SETTINGS_WINDOW)} + description={intl.string(t.REPLUGGED_SETTINGS_WINDOW_DESC)}> + {window.DiscordNative.process.platform === "linux" && ( + <> + { + setTitleBar(value); + restartModal(true); + }} + label={intl.string(t.REPLUGGED_SETTINGS_CUSTOM_TITLE_BAR)} + description={intl.format(t.REPLUGGED_SETTINGS_CUSTOM_TITLE_BAR_DESC, {})} + /> + + )} - { - setTransparency(value); - restartModal(true); - }} - label={intl.string(t.REPLUGGED_SETTINGS_TRANSPARENCY)} - description={intl.format(t.REPLUGGED_SETTINGS_TRANSPARENT_DESC, {})} - /> + + { + setTransparency(value); + restartModal(true); + }} + label={intl.string(t.REPLUGGED_SETTINGS_TRANSPARENT)} + description={intl.format(t.REPLUGGED_SETTINGS_TRANSPARENT_DESC, {})} + /> + {(window.DiscordNative.process.platform === "linux" || + window.DiscordNative.process.platform === "win32") && ( + + {window.DiscordNative.process.platform === "linux" + ? intl.format(t.REPLUGGED_SETTINGS_TRANSPARENT_ISSUES_LINUX, {}) + : intl.format(t.REPLUGGED_SETTINGS_TRANSPARENT_ISSUES_WINDOWS, {})} + + )} + + {window.DiscordNative.process.platform === "win32" && ( { - setBackgroundMaterial(value); - void window.RepluggedNative.transparency.setBackgroundMaterial(value); - }} - disabled={!transparency} - label={intl.string(t.REPLUGGED_SETTINGS_TRANSPARENCY_BG_MATERIAL)} - options={BACKGROUND_MATERIALS.map((m) => ({ - label: m.charAt(0).toUpperCase() + m.slice(1), - value: m, - }))} - /> + <> + + { - setVibrancy(value); - void window.RepluggedNative.transparency.setVibrancy(value); - }} - label={intl.string(t.REPLUGGED_SETTINGS_TRANSPARENCY_VIBRANCY)} - options={VIBRANCY_SELECT_OPTIONS} - clearable - /> + <> + + { setVibrancy(value);