diff --git a/.npmrc b/.npmrc index d67f374..eee7e86 100644 --- a/.npmrc +++ b/.npmrc @@ -1 +1,2 @@ node-linker=hoisted +package-manager-strict=false \ No newline at end of file diff --git a/package.json b/package.json index 81ee4b9..5bbec49 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "arrpc": "github:OpenAsar/arrpc#6960a8fd4d65d566da93dbdb8a7ca474aa0a3c9c" }, "optionalDependencies": { - "@vencord/venmic": "^3.4.2" + "@vencord/venmic": "^3.5.0" }, "devDependencies": { "@fal-works/esbuild-plugin-global-externals": "^2.1.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f7e1bd4..6221301 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -13,8 +13,8 @@ importers: version: https://codeload.github.com/OpenAsar/arrpc/tar.gz/6960a8fd4d65d566da93dbdb8a7ca474aa0a3c9c optionalDependencies: '@vencord/venmic': - specifier: ^3.4.2 - version: 3.4.2 + specifier: ^3.5.0 + version: 3.5.0 devDependencies: '@fal-works/esbuild-plugin-global-externals': specifier: ^2.1.2 @@ -603,8 +603,8 @@ packages: '@vencord/types@1.8.4': resolution: {integrity: sha512-ogLqIOHVO+5zxKUVxAfGIAUZoEfIomrlg6f0cZ/2yd5vBAn1fA9Gi/NASoKfHZuJt8ZcYw329bgn0ah/VufqMg==} - '@vencord/venmic@3.4.2': - resolution: {integrity: sha512-nwGjarof1wVvecGksGONfb+PduhY4gSuHTm39LktiQayHS69+yv4lepq4k1lxZzjOgFTy5VXfmJqxt+BpqoUUQ==} + '@vencord/venmic@3.5.0': + resolution: {integrity: sha512-kPvrPcIeMkuqQriuiQAJ9rEBeqGR2nmFBuUtbZRGyiNRF9RDAfWSJYqhHVm6F7wbcqrSZio6FazZuBo0LvjJRw==} engines: {node: '>=14.15'} os: [linux] @@ -678,6 +678,7 @@ packages: are-we-there-yet@3.0.1: resolution: {integrity: sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==} engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + deprecated: This package is no longer supported. argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} @@ -1400,6 +1401,7 @@ packages: gauge@4.0.4: resolution: {integrity: sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==} engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + deprecated: This package is no longer supported. get-caller-file@2.0.5: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} @@ -1937,6 +1939,7 @@ packages: npmlog@6.0.2: resolution: {integrity: sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==} engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + deprecated: This package is no longer supported. object-copy@0.1.0: resolution: {integrity: sha512-79LYn6VAb63zgtmAteVOWo9Vdj71ZVBy3Pbse+VqxDpEP83XuujMrGqHIwAXJ5I/aM0zU7dIyIAhifVTPrNItQ==} @@ -3028,7 +3031,7 @@ snapshots: standalone-electron-types: 1.0.0 type-fest: 3.13.1 - '@vencord/venmic@3.4.2': + '@vencord/venmic@3.5.0': dependencies: cmake-js: 7.3.0 node-addon-api: 8.0.0 diff --git a/src/main/constants.ts b/src/main/constants.ts index 1a02d5e..1bce303 100644 --- a/src/main/constants.ts +++ b/src/main/constants.ts @@ -5,11 +5,22 @@ */ import { app } from "electron"; -import { existsSync, readdirSync, renameSync, rmdirSync } from "fs"; -import { join } from "path"; +import { existsSync, mkdirSync, readdirSync, renameSync, rmdirSync } from "fs"; +import { dirname, join } from "path"; + +const vesktopDir = dirname(process.execPath); + +export const PORTABLE = + process.platform === "win32" && + !process.execPath.toLowerCase().endsWith("electron.exe") && + !existsSync(join(vesktopDir, "Uninstall Vesktop.exe")); const LEGACY_DATA_DIR = join(app.getPath("appData"), "VencordDesktop", "VencordDesktop"); -export const DATA_DIR = process.env.VENCORD_USER_DATA_DIR || join(app.getPath("userData")); +export const DATA_DIR = + process.env.VENCORD_USER_DATA_DIR || (PORTABLE ? join(vesktopDir, "Data") : join(app.getPath("userData"))); + +mkdirSync(DATA_DIR, { recursive: true }); + // TODO: remove eventually if (existsSync(LEGACY_DATA_DIR)) { try { diff --git a/src/main/index.ts b/src/main/index.ts index f0e67fd..a49d255 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -42,7 +42,13 @@ function init() { // disable renderer backgrounding to prevent the app from unloading when in the background // https://github.com/electron/electron/issues/2822 + // https://github.com/GoogleChrome/chrome-launcher/blob/5a27dd574d47a75fec0fb50f7b774ebf8a9791ba/docs/chrome-flags-for-tools.md#task-throttling app.commandLine.appendSwitch("disable-renderer-backgrounding"); + app.commandLine.appendSwitch("disable-background-timer-throttling"); + app.commandLine.appendSwitch("disable-backgrounding-occluded-windows"); + if (process.platform === "win32") { + disabledFeatures.push("CalculateNativeWinOcclusion"); + } // work around chrome 66 disabling autoplay by default app.commandLine.appendSwitch("autoplay-policy", "no-user-gesture-required"); @@ -50,12 +56,7 @@ function init() { // HardwareMediaKeyHandling,MediaSessionService: Prevent Discord from registering as a media service. // // WidgetLayering (Vencord Added): Fix DevTools context menus https://github.com/electron/electron/issues/38790 - disabledFeatures.push( - "WinRetrieveSuggestionsOnlyOnDemand", - "HardwareMediaKeyHandling", - "MediaSessionService", - "WidgetLayering" - ); + disabledFeatures.push("WinRetrieveSuggestionsOnlyOnDemand", "HardwareMediaKeyHandling", "MediaSessionService"); app.commandLine.appendSwitch("enable-features", [...new Set(enabledFeatures)].filter(Boolean).join(",")); app.commandLine.appendSwitch("disable-features", [...new Set(disabledFeatures)].filter(Boolean).join(",")); diff --git a/src/main/ipc.ts b/src/main/ipc.ts index 3bfca76..a20ff48 100644 --- a/src/main/ipc.ts +++ b/src/main/ipc.ts @@ -93,12 +93,8 @@ handle(IpcEvents.MAXIMIZE, e => { } }); -handle(IpcEvents.SPELLCHECK_SET_LANGUAGES, (_, languages: string[]) => { - const ses = session.defaultSession; - - const available = ses.availableSpellCheckerLanguages; - const applicable = languages.filter(l => available.includes(l)).slice(0, 3); - if (applicable.length) ses.setSpellCheckerLanguages(applicable); +handleSync(IpcEvents.SPELLCHECK_GET_AVAILABLE_LANGUAGES, e => { + e.returnValue = session.defaultSession.availableSpellCheckerLanguages; }); handle(IpcEvents.SPELLCHECK_REPLACE_MISSPELLING, (e, word: string) => { diff --git a/src/main/mainWindow.ts b/src/main/mainWindow.ts index 3ee1fd0..12772d9 100644 --- a/src/main/mainWindow.ts +++ b/src/main/mainWindow.ts @@ -13,6 +13,7 @@ import { MenuItemConstructorOptions, nativeTheme, screen, + session, Tray } from "electron"; import { rm } from "fs/promises"; @@ -360,12 +361,27 @@ function initSettingsListeners(win: BrowserWindow) { addSettingsListener("enableMenu", enabled => { win.setAutoHideMenuBar(enabled ?? false); }); + + addSettingsListener("spellCheckLanguages", languages => initSpellCheckLanguages(win, languages)); +} + +async function initSpellCheckLanguages(win: BrowserWindow, languages?: string[]) { + languages ??= await win.webContents.executeJavaScript("[...new Set(navigator.languages)]").catch(() => []); + if (!languages) return; + + const ses = session.defaultSession; + + const available = ses.availableSpellCheckerLanguages; + const applicable = languages.filter(l => available.includes(l)).slice(0, 5); + if (applicable.length) ses.setSpellCheckerLanguages(applicable); } function initSpellCheck(win: BrowserWindow) { win.webContents.on("context-menu", (_, data) => { win.webContents.send(IpcEvents.SPELLCHECK_RESULT, data.misspelledWord, data.dictionarySuggestions); }); + + initSpellCheckLanguages(win, Settings.store.spellCheckLanguages); } function createMainWindow() { @@ -387,7 +403,9 @@ function createMainWindow() { contextIsolation: true, devTools: true, preload: join(__dirname, "preload.js"), - spellcheck: true + spellcheck: true, + // disable renderer backgrounding to prevent the app from unloading when in the background + backgroundThrottling: false }, icon: ICON_PATH, frame: !noFrame, diff --git a/src/main/settings.ts b/src/main/settings.ts index 03c705e..f2c1b80 100644 --- a/src/main/settings.ts +++ b/src/main/settings.ts @@ -35,11 +35,6 @@ function loadSettings(file: string, name: string) { } export const Settings = loadSettings(SETTINGS_FILE, "Vesktop settings"); -if (Object.hasOwn(Settings.plain, "discordWindowsTitleBar")) { - Settings.plain.customTitleBar = Settings.plain.discordWindowsTitleBar; - delete Settings.plain.discordWindowsTitleBar; - Settings.markAsChanged(); -} export const VencordSettings = loadSettings(VENCORD_SETTINGS_FILE, "Vencord settings"); diff --git a/src/preload/VesktopNative.ts b/src/preload/VesktopNative.ts index af4b9f1..530df38 100644 --- a/src/preload/VesktopNative.ts +++ b/src/preload/VesktopNative.ts @@ -41,7 +41,7 @@ export const VesktopNative = { set: (settings: Settings, path?: string) => invoke(IpcEvents.SET_SETTINGS, settings, path) }, spellcheck: { - setLanguages: (languages: readonly string[]) => invoke(IpcEvents.SPELLCHECK_SET_LANGUAGES, languages), + getAvailableLanguages: () => sendSync(IpcEvents.SPELLCHECK_GET_AVAILABLE_LANGUAGES), onSpellcheckResult(cb: SpellCheckerResultCallback) { spellCheckCallbacks.add(cb); }, diff --git a/src/preload/index.ts b/src/preload/index.ts index 2cc0d11..75bf9cd 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -40,5 +40,3 @@ if (IS_DEV) { }); } // #endregion - -VesktopNative.spellcheck.setLanguages(window.navigator.languages); diff --git a/src/renderer/index.ts b/src/renderer/index.ts index 1ccc2e4..99dca25 100644 --- a/src/renderer/index.ts +++ b/src/renderer/index.ts @@ -12,7 +12,7 @@ import "./themedSplash"; console.log("read if cute :3"); export * as Components from "./components"; -import { findByPropsLazy } from "@vencord/types/webpack"; +import { findByPropsLazy, onceReady } from "@vencord/types/webpack"; import { FluxDispatcher } from "@vencord/types/webpack/common"; import SettingsUi from "./components/settings/Settings"; @@ -52,8 +52,10 @@ const arRPC = Vencord.Plugins.plugins["WebRichPresence (arRPC)"] as any as { handleEvent(e: MessageEvent): void; }; -VesktopNative.arrpc.onActivity(data => { +VesktopNative.arrpc.onActivity(async data => { if (!Settings.store.arRPC) return; + await onceReady; + arRPC.handleEvent(new MessageEvent("message", { data })); }); diff --git a/src/renderer/patches/spellCheck.tsx b/src/renderer/patches/spellCheck.tsx index 9f0dbbd..040d41b 100644 --- a/src/renderer/patches/spellCheck.tsx +++ b/src/renderer/patches/spellCheck.tsx @@ -6,7 +6,8 @@ import { addContextMenuPatch } from "@vencord/types/api/ContextMenu"; import { findStoreLazy } from "@vencord/types/webpack"; -import { FluxDispatcher, Menu, useStateFromStores } from "@vencord/types/webpack/common"; +import { FluxDispatcher, Menu, useMemo, useStateFromStores } from "@vencord/types/webpack/common"; +import { useSettings } from "renderer/settings"; import { addPatch } from "./shared"; @@ -50,7 +51,16 @@ addContextMenuPatch("textarea-context", children => { const spellCheckEnabled = useStateFromStores([SpellCheckStore], () => SpellCheckStore.isEnabled()); const hasCorrections = Boolean(word && corrections?.length); - children.push( + const availableLanguages = useMemo(VesktopNative.spellcheck.getAvailableLanguages, []); + + const settings = useSettings(); + const spellCheckLanguages = (settings.spellCheckLanguages ??= [...new Set(navigator.languages)]); + + const pasteSectionIndex = children.findIndex(c => c?.props?.children?.some(c => c?.props?.id === "paste")); + + children.splice( + pasteSectionIndex === -1 ? children.length : pasteSectionIndex, + 0, {hasCorrections && ( <> @@ -69,14 +79,39 @@ addContextMenuPatch("textarea-context", children => { /> )} - { - FluxDispatcher.dispatch({ type: "SPELLCHECK_TOGGLE" }); - }} - /> + + + { + FluxDispatcher.dispatch({ type: "SPELLCHECK_TOGGLE" }); + }} + /> + + + {availableLanguages.map(lang => { + const isEnabled = spellCheckLanguages.includes(lang); + return ( + = 5} + action={() => { + const newSpellCheckLanguages = spellCheckLanguages.filter(l => l !== lang); + if (newSpellCheckLanguages.length === spellCheckLanguages.length) { + newSpellCheckLanguages.push(lang); + } + + settings.spellCheckLanguages = newSpellCheckLanguages; + }} + /> + ); + })} + + ); }); diff --git a/src/shared/IpcEvents.ts b/src/shared/IpcEvents.ts index b5d40c4..8d22b9e 100644 --- a/src/shared/IpcEvents.ts +++ b/src/shared/IpcEvents.ts @@ -30,7 +30,7 @@ export const enum IpcEvents { UPDATER_DOWNLOAD = "VCD_UPDATER_DOWNLOAD", UPDATE_IGNORE = "VCD_UPDATE_IGNORE", - SPELLCHECK_SET_LANGUAGES = "VCD_SPELLCHECK_SET_LANGUAGES", + SPELLCHECK_GET_AVAILABLE_LANGUAGES = "VCD_SPELLCHECK_GET_AVAILABLE_LANGUAGES", SPELLCHECK_RESULT = "VCD_SPELLCHECK_RESULT", SPELLCHECK_REPLACE_MISSPELLING = "VCD_SPELLCHECK_REPLACE_MISSPELLING", SPELLCHECK_ADD_TO_DICTIONARY = "VCD_SPELLCHECK_ADD_TO_DICTIONARY", diff --git a/src/shared/settings.d.ts b/src/shared/settings.d.ts index 378b625..5462b24 100644 --- a/src/shared/settings.d.ts +++ b/src/shared/settings.d.ts @@ -21,8 +21,6 @@ export interface Settings { appBadge?: boolean; disableMinSize?: boolean; clickTrayToShowHide?: boolean; - /** @deprecated use customTitleBar */ - discordWindowsTitleBar?: boolean; customTitleBar?: boolean; checkUpdates?: boolean; @@ -31,6 +29,8 @@ export interface Settings { splashColor?: string; splashAnimationPath?: string; splashBackground?: string; + + spellCheckLanguages?: string[]; } export interface State { diff --git a/src/updater/main.ts b/src/updater/main.ts index 207687e..4c19ffd 100644 --- a/src/updater/main.ts +++ b/src/updater/main.ts @@ -5,6 +5,7 @@ */ import { app, BrowserWindow, shell } from "electron"; +import { PORTABLE } from "main/constants"; import { Settings, State } from "main/settings"; import { handle } from "main/utils/ipcWrappers"; import { makeLinksOpenExternally } from "main/utils/makeLinksOpenExternally"; @@ -23,17 +24,12 @@ let updateData: UpdateData; handle(IpcEvents.UPDATER_GET_DATA, () => updateData); handle(IpcEvents.UPDATER_DOWNLOAD, () => { - const portable = !!process.env.PORTABLE_EXECUTABLE_FILE; - const { assets } = updateData.release; const url = (() => { switch (process.platform) { case "win32": return assets.find(a => { - if (!a.name.endsWith(".exe")) return false; - - const isSetup = a.name.includes("Setup"); - return portable ? !isSetup : isSetup; + return a.name.endsWith(PORTABLE ? "win.zip" : ".exe"); })!.browser_download_url; case "darwin": return assets.find(a =>