parent
c387979be1
commit
27c46d0b8e
@ -0,0 +1,73 @@ |
|||||||
|
/* |
||||||
|
* SPDX-License-Identifier: GPL-3.0 |
||||||
|
* Aerocord, a vesktop fork for older microsoft NT releases such as NT 6.0, 6.1, 6.2 and 6.3.
|
||||||
|
* Credits to vendicated and the rest of the vesktop contribuitors for making Vesktop! |
||||||
|
*/ |
||||||
|
|
||||||
|
import { app } from "electron"; |
||||||
|
import { BrowserWindow } from "electron/main"; |
||||||
|
import { copyFileSync, mkdirSync, readdirSync } from "fs"; |
||||||
|
import { join } from "path"; |
||||||
|
import { SplashProps } from "shared/browserWinProperties"; |
||||||
|
import { ICON_PATH, VIEW_DIR } from "shared/paths"; |
||||||
|
|
||||||
|
import { autoStart } from "./autoStart"; |
||||||
|
import { DATA_DIR } from "./constants"; |
||||||
|
import { createWindows } from "./mainWindow"; |
||||||
|
import { Settings, State } from "./settings"; |
||||||
|
import { makeLinksOpenExternally } from "./utils/makeLinksOpenExternally"; |
||||||
|
|
||||||
|
interface Data { |
||||||
|
minimizeToTray: boolean; |
||||||
|
discordBranch: "stable" | "canary" | "ptb"; |
||||||
|
autoStart: boolean; |
||||||
|
importSettings: boolean; |
||||||
|
richPresence: boolean; |
||||||
|
} |
||||||
|
|
||||||
|
export function createFirstLaunchTour() { |
||||||
|
const win = new BrowserWindow({ |
||||||
|
...SplashProps, |
||||||
|
frame: true, |
||||||
|
autoHideMenuBar: true, |
||||||
|
height: 470, |
||||||
|
width: 550, |
||||||
|
icon: ICON_PATH |
||||||
|
}); |
||||||
|
|
||||||
|
makeLinksOpenExternally(win); |
||||||
|
|
||||||
|
win.loadFile(join(VIEW_DIR, "first-launch.html")); |
||||||
|
win.webContents.addListener("console-message", (_e, _l, msg) => { |
||||||
|
if (msg === "cancel") return app.exit(); |
||||||
|
|
||||||
|
if (!msg.startsWith("form:")) return; |
||||||
|
const data = JSON.parse(msg.slice(5)) as Data; |
||||||
|
|
||||||
|
State.store.firstLaunch = false; |
||||||
|
Settings.store.minimizeToTray = data.minimizeToTray; |
||||||
|
Settings.store.discordBranch = data.discordBranch; |
||||||
|
Settings.store.arRPC = data.richPresence; |
||||||
|
|
||||||
|
if (data.autoStart) autoStart.enable(); |
||||||
|
|
||||||
|
if (data.importSettings) { |
||||||
|
const from = join(app.getPath("userData"), "..", "Vencord", "settings"); |
||||||
|
const to = join(DATA_DIR, "settings"); |
||||||
|
try { |
||||||
|
const files = readdirSync(from); |
||||||
|
mkdirSync(to, { recursive: true }); |
||||||
|
|
||||||
|
for (const file of files) { |
||||||
|
copyFileSync(join(from, file), join(to, file)); |
||||||
|
} |
||||||
|
} catch (e) { |
||||||
|
console.error("Failed to import settings:", e); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
win.close(); |
||||||
|
|
||||||
|
createWindows(); |
||||||
|
}); |
||||||
|
} |
@ -0,0 +1,108 @@ |
|||||||
|
/* |
||||||
|
* SPDX-License-Identifier: GPL-3.0 |
||||||
|
* Aerocord, a vesktop fork for older microsoft NT releases such as NT 6.0, 6.1, 6.2 and 6.3.
|
||||||
|
* Credits to vendicated and the rest of the vesktop contribuitors for making Vesktop! |
||||||
|
*/ |
||||||
|
|
||||||
|
import "./ipc"; |
||||||
|
|
||||||
|
import { app, BrowserWindow, nativeTheme } from "electron"; |
||||||
|
import { checkUpdates } from "updater/main"; |
||||||
|
|
||||||
|
import { DATA_DIR } from "./constants"; |
||||||
|
import { createFirstLaunchTour } from "./firstLaunch"; |
||||||
|
import { createWindows, mainWin } from "./mainWindow"; |
||||||
|
import { registerMediaPermissionsHandler } from "./mediaPermissions"; |
||||||
|
import { registerScreenShareHandler } from "./screenShare"; |
||||||
|
import { Settings, State } from "./settings"; |
||||||
|
import { isDeckGameMode } from "./utils/steamOS"; |
||||||
|
|
||||||
|
if (IS_DEV) { |
||||||
|
require("source-map-support").install(); |
||||||
|
} |
||||||
|
|
||||||
|
// Make the Vencord files use our DATA_DIR
|
||||||
|
process.env.VENCORD_USER_DATA_DIR = DATA_DIR; |
||||||
|
|
||||||
|
function init() { |
||||||
|
const { disableSmoothScroll, hardwareAcceleration } = Settings.store; |
||||||
|
|
||||||
|
const enabledFeatures = app.commandLine.getSwitchValue("enable-features").split(","); |
||||||
|
const disabledFeatures = app.commandLine.getSwitchValue("disable-features").split(","); |
||||||
|
|
||||||
|
if (hardwareAcceleration === false) { |
||||||
|
app.disableHardwareAcceleration(); |
||||||
|
} else { |
||||||
|
enabledFeatures.push("VaapiVideoDecodeLinuxGL", "VaapiVideoEncoder", "VaapiVideoDecoder"); |
||||||
|
} |
||||||
|
|
||||||
|
if (disableSmoothScroll) { |
||||||
|
app.commandLine.appendSwitch("disable-smooth-scrolling"); |
||||||
|
} |
||||||
|
|
||||||
|
// work around chrome 66 disabling autoplay by default
|
||||||
|
app.commandLine.appendSwitch("autoplay-policy", "no-user-gesture-required"); |
||||||
|
// WinRetrieveSuggestionsOnlyOnDemand: Work around electron 13 bug w/ async spellchecking on Windows.
|
||||||
|
// 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" |
||||||
|
); |
||||||
|
|
||||||
|
app.commandLine.appendSwitch("enable-features", [...new Set(enabledFeatures)].filter(Boolean).join(",")); |
||||||
|
app.commandLine.appendSwitch("disable-features", [...new Set(disabledFeatures)].filter(Boolean).join(",")); |
||||||
|
|
||||||
|
// In the Flatpak on SteamOS the theme is detected as light, but SteamOS only has a dark mode, so we just override it
|
||||||
|
if (isDeckGameMode) nativeTheme.themeSource = "dark"; |
||||||
|
|
||||||
|
app.on("second-instance", (_event, _cmdLine, _cwd, data: any) => { |
||||||
|
if (data.IS_DEV) app.quit(); |
||||||
|
else if (mainWin) { |
||||||
|
if (mainWin.isMinimized()) mainWin.restore(); |
||||||
|
if (!mainWin.isVisible()) mainWin.show(); |
||||||
|
mainWin.focus(); |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
app.whenReady().then(async () => { |
||||||
|
checkUpdates(); |
||||||
|
if (process.platform === "win32") app.setAppUserModelId("dev.vencord.vesktop"); |
||||||
|
|
||||||
|
registerScreenShareHandler(); |
||||||
|
registerMediaPermissionsHandler(); |
||||||
|
|
||||||
|
bootstrap(); |
||||||
|
|
||||||
|
app.on("activate", () => { |
||||||
|
if (BrowserWindow.getAllWindows().length === 0) createWindows(); |
||||||
|
}); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
if (!app.requestSingleInstanceLock({ IS_DEV })) { |
||||||
|
if (IS_DEV) { |
||||||
|
console.log("Vesktop is already running. Quitting previous instance..."); |
||||||
|
init(); |
||||||
|
} else { |
||||||
|
console.log("Vesktop is already running. Quitting..."); |
||||||
|
app.quit(); |
||||||
|
} |
||||||
|
} else { |
||||||
|
init(); |
||||||
|
} |
||||||
|
|
||||||
|
async function bootstrap() { |
||||||
|
if (!Object.hasOwn(State.store, "firstLaunch")) { |
||||||
|
createFirstLaunchTour(); |
||||||
|
} else { |
||||||
|
createWindows(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
app.on("window-all-closed", () => { |
||||||
|
if (process.platform !== "darwin") app.quit(); |
||||||
|
}); |
@ -0,0 +1,155 @@ |
|||||||
|
/* |
||||||
|
* SPDX-License-Identifier: GPL-3.0 |
||||||
|
* Aerocord, a vesktop fork for older microsoft NT releases such as NT 6.0, 6.1, 6.2 and 6.3.
|
||||||
|
* Credits to vendicated and the rest of the vesktop contribuitors for making Vesktop! |
||||||
|
*/ |
||||||
|
|
||||||
|
if (process.platform === "linux") import("./venmic"); |
||||||
|
|
||||||
|
import { execFile } from "child_process"; |
||||||
|
import { app, BrowserWindow, clipboard, dialog, nativeImage, RelaunchOptions, session, shell } from "electron"; |
||||||
|
import { mkdirSync, readFileSync, watch } from "fs"; |
||||||
|
import { open, readFile } from "fs/promises"; |
||||||
|
import { release } from "os"; |
||||||
|
import { join } from "path"; |
||||||
|
import { debounce } from "shared/utils/debounce"; |
||||||
|
|
||||||
|
import { IpcEvents } from "../shared/IpcEvents"; |
||||||
|
import { setBadgeCount } from "./appBadge"; |
||||||
|
import { autoStart } from "./autoStart"; |
||||||
|
import { VENCORD_FILES_DIR, VENCORD_QUICKCSS_FILE, VENCORD_THEMES_DIR } from "./constants"; |
||||||
|
import { mainWin } from "./mainWindow"; |
||||||
|
import { Settings } from "./settings"; |
||||||
|
import { handle, handleSync } from "./utils/ipcWrappers"; |
||||||
|
import { PopoutWindows } from "./utils/popout"; |
||||||
|
import { isDeckGameMode, showGamePage } from "./utils/steamOS"; |
||||||
|
import { isValidVencordInstall } from "./utils/vencordLoader"; |
||||||
|
|
||||||
|
handleSync(IpcEvents.GET_VENCORD_PRELOAD_FILE, () => join(VENCORD_FILES_DIR, "vencordDesktopPreload.js")); |
||||||
|
handleSync(IpcEvents.GET_VENCORD_RENDERER_SCRIPT, () => |
||||||
|
readFileSync(join(VENCORD_FILES_DIR, "vencordDesktopRenderer.js"), "utf-8") |
||||||
|
); |
||||||
|
|
||||||
|
handleSync(IpcEvents.GET_RENDERER_SCRIPT, () => readFileSync(join(__dirname, "renderer.js"), "utf-8")); |
||||||
|
handleSync(IpcEvents.GET_RENDERER_CSS_FILE, () => join(__dirname, "renderer.css")); |
||||||
|
|
||||||
|
handleSync(IpcEvents.GET_SETTINGS, () => Settings.plain); |
||||||
|
handleSync(IpcEvents.GET_VERSION, () => app.getVersion()); |
||||||
|
|
||||||
|
handleSync( |
||||||
|
IpcEvents.SUPPORTS_WINDOWS_TRANSPARENCY, |
||||||
|
() => process.platform === "win32" && Number(release().split(".").pop()) >= 22621 |
||||||
|
); |
||||||
|
|
||||||
|
handleSync(IpcEvents.AUTOSTART_ENABLED, () => autoStart.isEnabled()); |
||||||
|
handle(IpcEvents.ENABLE_AUTOSTART, autoStart.enable); |
||||||
|
handle(IpcEvents.DISABLE_AUTOSTART, autoStart.disable); |
||||||
|
|
||||||
|
handle(IpcEvents.SET_SETTINGS, (_, settings: typeof Settings.store, path?: string) => { |
||||||
|
Settings.setData(settings, path); |
||||||
|
}); |
||||||
|
|
||||||
|
handle(IpcEvents.RELAUNCH, async () => { |
||||||
|
const options: RelaunchOptions = { |
||||||
|
args: process.argv.slice(1).concat(["--relaunch"]) |
||||||
|
}; |
||||||
|
if (isDeckGameMode) { |
||||||
|
// We can't properly relaunch when running under gamescope, but we can at least navigate to our page in Steam.
|
||||||
|
await showGamePage(); |
||||||
|
} else if (app.isPackaged && process.env.APPIMAGE) { |
||||||
|
execFile(process.env.APPIMAGE, options.args); |
||||||
|
} else { |
||||||
|
app.relaunch(options); |
||||||
|
} |
||||||
|
app.exit(); |
||||||
|
}); |
||||||
|
|
||||||
|
handle(IpcEvents.SHOW_ITEM_IN_FOLDER, (_, path) => { |
||||||
|
shell.showItemInFolder(path); |
||||||
|
}); |
||||||
|
|
||||||
|
handle(IpcEvents.FOCUS, () => { |
||||||
|
mainWin.show(); |
||||||
|
mainWin.setSkipTaskbar(false); |
||||||
|
}); |
||||||
|
|
||||||
|
handle(IpcEvents.CLOSE, (e, key?: string) => { |
||||||
|
const popout = PopoutWindows.get(key!); |
||||||
|
if (popout) return popout.close(); |
||||||
|
|
||||||
|
const win = BrowserWindow.fromWebContents(e.sender) ?? e.sender; |
||||||
|
win.close(); |
||||||
|
}); |
||||||
|
|
||||||
|
handle(IpcEvents.MINIMIZE, e => { |
||||||
|
mainWin.minimize(); |
||||||
|
}); |
||||||
|
|
||||||
|
handle(IpcEvents.MAXIMIZE, e => { |
||||||
|
if (mainWin.isMaximized()) { |
||||||
|
mainWin.unmaximize(); |
||||||
|
} else { |
||||||
|
mainWin.maximize(); |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
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); |
||||||
|
}); |
||||||
|
|
||||||
|
handle(IpcEvents.SPELLCHECK_REPLACE_MISSPELLING, (e, word: string) => { |
||||||
|
e.sender.replaceMisspelling(word); |
||||||
|
}); |
||||||
|
|
||||||
|
handle(IpcEvents.SPELLCHECK_ADD_TO_DICTIONARY, (e, word: string) => { |
||||||
|
e.sender.session.addWordToSpellCheckerDictionary(word); |
||||||
|
}); |
||||||
|
|
||||||
|
handle(IpcEvents.SELECT_VENCORD_DIR, async () => { |
||||||
|
const res = await dialog.showOpenDialog(mainWin!, { |
||||||
|
properties: ["openDirectory"] |
||||||
|
}); |
||||||
|
if (!res.filePaths.length) return "cancelled"; |
||||||
|
|
||||||
|
const dir = res.filePaths[0]; |
||||||
|
if (!isValidVencordInstall(dir)) return "invalid"; |
||||||
|
|
||||||
|
return dir; |
||||||
|
}); |
||||||
|
|
||||||
|
handle(IpcEvents.SET_BADGE_COUNT, (_, count: number) => setBadgeCount(count)); |
||||||
|
|
||||||
|
handle(IpcEvents.CLIPBOARD_COPY_IMAGE, async (_, buf: ArrayBuffer, src: string) => { |
||||||
|
clipboard.write({ |
||||||
|
html: `<img src="${src.replaceAll('"', '\\"')}">`, |
||||||
|
image: nativeImage.createFromBuffer(Buffer.from(buf)) |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
function readCss() { |
||||||
|
return readFile(VENCORD_QUICKCSS_FILE, "utf-8").catch(() => ""); |
||||||
|
} |
||||||
|
|
||||||
|
open(VENCORD_QUICKCSS_FILE, "a+").then(fd => { |
||||||
|
fd.close(); |
||||||
|
watch( |
||||||
|
VENCORD_QUICKCSS_FILE, |
||||||
|
{ persistent: false }, |
||||||
|
debounce(async () => { |
||||||
|
mainWin?.webContents.postMessage("VencordQuickCssUpdate", await readCss()); |
||||||
|
}, 50) |
||||||
|
); |
||||||
|
}); |
||||||
|
|
||||||
|
mkdirSync(VENCORD_THEMES_DIR, { recursive: true }); |
||||||
|
watch( |
||||||
|
VENCORD_THEMES_DIR, |
||||||
|
{ persistent: false }, |
||||||
|
debounce(() => { |
||||||
|
mainWin?.webContents.postMessage("VencordThemeUpdate", void 0); |
||||||
|
}) |
||||||
|
); |
@ -0,0 +1,488 @@ |
|||||||
|
/* |
||||||
|
* SPDX-License-Identifier: GPL-3.0 |
||||||
|
* Aerocord, a vesktop fork for older microsoft NT releases such as NT 6.0, 6.1, 6.2 and 6.3.
|
||||||
|
* Credits to vendicated and the rest of the vesktop contribuitors for making Vesktop! |
||||||
|
*/ |
||||||
|
|
||||||
|
import { |
||||||
|
app, |
||||||
|
BrowserWindow, |
||||||
|
BrowserWindowConstructorOptions, |
||||||
|
dialog, |
||||||
|
Menu, |
||||||
|
MenuItemConstructorOptions, |
||||||
|
nativeTheme, |
||||||
|
shell, |
||||||
|
Tray |
||||||
|
} from "electron"; |
||||||
|
import { rm } from "fs/promises"; |
||||||
|
import { join } from "path"; |
||||||
|
import { IpcEvents } from "shared/IpcEvents"; |
||||||
|
import { isTruthy } from "shared/utils/guards"; |
||||||
|
import { once } from "shared/utils/once"; |
||||||
|
import type { SettingsStore } from "shared/utils/SettingsStore"; |
||||||
|
|
||||||
|
import { ICON_PATH } from "../shared/paths"; |
||||||
|
import { createAboutWindow } from "./about"; |
||||||
|
import { initArRPC } from "./arrpc"; |
||||||
|
import { |
||||||
|
BrowserUserAgent, |
||||||
|
DATA_DIR, |
||||||
|
DEFAULT_HEIGHT, |
||||||
|
DEFAULT_WIDTH, |
||||||
|
MessageBoxChoice, |
||||||
|
MIN_HEIGHT, |
||||||
|
MIN_WIDTH, |
||||||
|
VENCORD_FILES_DIR |
||||||
|
} from "./constants"; |
||||||
|
import { Settings, State, VencordSettings } from "./settings"; |
||||||
|
import { createSplashWindow } from "./splash"; |
||||||
|
import { makeLinksOpenExternally } from "./utils/makeLinksOpenExternally"; |
||||||
|
import { applyDeckKeyboardFix, askToApplySteamLayout, isDeckGameMode } from "./utils/steamOS"; |
||||||
|
import { downloadVencordFiles, ensureVencordFiles } from "./utils/vencordLoader"; |
||||||
|
|
||||||
|
let isQuitting = false; |
||||||
|
let tray: Tray; |
||||||
|
|
||||||
|
applyDeckKeyboardFix(); |
||||||
|
|
||||||
|
app.on("before-quit", () => { |
||||||
|
isQuitting = true; |
||||||
|
}); |
||||||
|
|
||||||
|
export let mainWin: BrowserWindow; |
||||||
|
|
||||||
|
function makeSettingsListenerHelpers<O extends object>(o: SettingsStore<O>) { |
||||||
|
const listeners = new Map<(data: any) => void, PropertyKey>(); |
||||||
|
|
||||||
|
const addListener: typeof o.addChangeListener = (path, cb) => { |
||||||
|
listeners.set(cb, path); |
||||||
|
o.addChangeListener(path, cb); |
||||||
|
}; |
||||||
|
const removeAllListeners = () => { |
||||||
|
for (const [listener, path] of listeners) { |
||||||
|
o.removeChangeListener(path as any, listener); |
||||||
|
} |
||||||
|
|
||||||
|
listeners.clear(); |
||||||
|
}; |
||||||
|
|
||||||
|
return [addListener, removeAllListeners] as const; |
||||||
|
} |
||||||
|
|
||||||
|
const [addSettingsListener, removeSettingsListeners] = makeSettingsListenerHelpers(Settings); |
||||||
|
const [addVencordSettingsListener, removeVencordSettingsListeners] = makeSettingsListenerHelpers(VencordSettings); |
||||||
|
|
||||||
|
function initTray(win: BrowserWindow) { |
||||||
|
const onTrayClick = () => { |
||||||
|
if (Settings.store.clickTrayToShowHide && win.isVisible()) win.hide(); |
||||||
|
else win.show(); |
||||||
|
}; |
||||||
|
const trayMenu = Menu.buildFromTemplate([ |
||||||
|
{ |
||||||
|
label: "Open", |
||||||
|
click() { |
||||||
|
win.show(); |
||||||
|
} |
||||||
|
}, |
||||||
|
{ |
||||||
|
label: "About and More", |
||||||
|
click: createAboutWindow |
||||||
|
}, |
||||||
|
{ |
||||||
|
label: "Fix/Repair Vencord", |
||||||
|
async click() { |
||||||
|
await downloadVencordFiles(); |
||||||
|
app.relaunch(); |
||||||
|
app.quit(); |
||||||
|
} |
||||||
|
}, |
||||||
|
{ |
||||||
|
label: "Reset User Data", |
||||||
|
async click() { |
||||||
|
await clearData(win); |
||||||
|
} |
||||||
|
}, |
||||||
|
{ |
||||||
|
type: "separator" |
||||||
|
}, |
||||||
|
{ |
||||||
|
label: "Open Updater", |
||||||
|
click() { |
||||||
|
const updaterPath = join(app.getPath('exe'), '..', 'Updater.exe'); |
||||||
|
shell.openPath(updaterPath); |
||||||
|
} |
||||||
|
}, |
||||||
|
{ |
||||||
|
label: "Restart Aerocord", |
||||||
|
click() { |
||||||
|
app.relaunch(); |
||||||
|
app.quit(); |
||||||
|
} |
||||||
|
}, |
||||||
|
{ |
||||||
|
label: "Close Aerocord", |
||||||
|
click() { |
||||||
|
isQuitting = true; |
||||||
|
app.quit(); |
||||||
|
} |
||||||
|
} |
||||||
|
]); |
||||||
|
|
||||||
|
tray = new Tray(ICON_PATH); |
||||||
|
tray.setToolTip("Aerocord"); |
||||||
|
tray.setContextMenu(trayMenu); |
||||||
|
tray.on("click", onTrayClick); |
||||||
|
} |
||||||
|
|
||||||
|
async function clearData(win: BrowserWindow) { |
||||||
|
const { response } = await dialog.showMessageBox(win, { |
||||||
|
message: "Are you sure you want to reset Aerocord?", |
||||||
|
detail: "This will log you out, clear caches and reset all your settings!\n\nAerocord will automatically restart after this operation.", |
||||||
|
buttons: ["Yes", "No"], |
||||||
|
cancelId: MessageBoxChoice.Cancel, |
||||||
|
defaultId: MessageBoxChoice.Default, |
||||||
|
type: "warning" |
||||||
|
}); |
||||||
|
|
||||||
|
if (response === MessageBoxChoice.Cancel) return; |
||||||
|
|
||||||
|
win.close(); |
||||||
|
|
||||||
|
await win.webContents.session.clearStorageData(); |
||||||
|
await win.webContents.session.clearCache(); |
||||||
|
await win.webContents.session.clearCodeCaches({}); |
||||||
|
await rm(DATA_DIR, { force: true, recursive: true }); |
||||||
|
|
||||||
|
app.relaunch(); |
||||||
|
app.quit(); |
||||||
|
} |
||||||
|
|
||||||
|
type MenuItemList = Array<MenuItemConstructorOptions | false>; |
||||||
|
|
||||||
|
function initMenuBar(win: BrowserWindow) { |
||||||
|
const isWindows = process.platform === "win32"; |
||||||
|
const isDarwin = process.platform === "darwin"; |
||||||
|
const wantCtrlQ = !isWindows || VencordSettings.store.winCtrlQ; |
||||||
|
|
||||||
|
const subMenu = [ |
||||||
|
{ |
||||||
|
label: "About and Documentation", |
||||||
|
click: createAboutWindow |
||||||
|
}, |
||||||
|
{ |
||||||
|
label: "Force Update Vencord", |
||||||
|
async click() { |
||||||
|
await downloadVencordFiles(); |
||||||
|
app.relaunch(); |
||||||
|
app.quit(); |
||||||
|
}, |
||||||
|
toolTip: "Aerocord will automatically restart after this operation" |
||||||
|
}, |
||||||
|
{ |
||||||
|
label: "Reset Aerocord", |
||||||
|
async click() { |
||||||
|
await clearData(win); |
||||||
|
}, |
||||||
|
toolTip: "Aerocord will automatically restart after this operation" |
||||||
|
}, |
||||||
|
{ |
||||||
|
label: "Relaunch", |
||||||
|
accelerator: "CmdOrCtrl+Shift+R", |
||||||
|
click() { |
||||||
|
app.relaunch(); |
||||||
|
app.quit(); |
||||||
|
} |
||||||
|
}, |
||||||
|
...(!isDarwin |
||||||
|
? [] |
||||||
|
: ([ |
||||||
|
{ |
||||||
|
type: "separator" |
||||||
|
}, |
||||||
|
{ |
||||||
|
label: "Settings", |
||||||
|
accelerator: "CmdOrCtrl+,", |
||||||
|
async click() { |
||||||
|
mainWin.webContents.executeJavaScript( |
||||||
|
"Vencord.Webpack.Common.SettingsRouter.open('My Account')" |
||||||
|
); |
||||||
|
} |
||||||
|
}, |
||||||
|
{ |
||||||
|
type: "separator" |
||||||
|
}, |
||||||
|
{ |
||||||
|
role: "hide" |
||||||
|
}, |
||||||
|
{ |
||||||
|
role: "hideOthers" |
||||||
|
}, |
||||||
|
{ |
||||||
|
role: "unhide" |
||||||
|
}, |
||||||
|
{ |
||||||
|
type: "separator" |
||||||
|
} |
||||||
|
] satisfies MenuItemList)), |
||||||
|
{ |
||||||
|
label: "Quit", |
||||||
|
accelerator: wantCtrlQ ? "CmdOrCtrl+Q" : void 0, |
||||||
|
visible: !isWindows, |
||||||
|
role: "quit", |
||||||
|
click() { |
||||||
|
app.quit(); |
||||||
|
} |
||||||
|
}, |
||||||
|
isWindows && { |
||||||
|
label: "Quit", |
||||||
|
accelerator: "Alt+F4", |
||||||
|
role: "quit", |
||||||
|
click() { |
||||||
|
app.quit(); |
||||||
|
} |
||||||
|
}, |
||||||
|
// See https://github.com/electron/electron/issues/14742 and https://github.com/electron/electron/issues/5256
|
||||||
|
{ |
||||||
|
label: "Zoom in (hidden, hack for Qwertz and others)", |
||||||
|
accelerator: "CmdOrCtrl+=", |
||||||
|
role: "zoomIn", |
||||||
|
visible: false |
||||||
|
} |
||||||
|
] satisfies MenuItemList; |
||||||
|
|
||||||
|
const menu = Menu.buildFromTemplate([ |
||||||
|
{ |
||||||
|
label: "Vesktop", |
||||||
|
role: "appMenu", |
||||||
|
submenu: subMenu.filter(isTruthy) |
||||||
|
}, |
||||||
|
{ role: "fileMenu" }, |
||||||
|
{ role: "editMenu" }, |
||||||
|
{ role: "viewMenu" }, |
||||||
|
{ role: "windowMenu" } |
||||||
|
]); |
||||||
|
|
||||||
|
Menu.setApplicationMenu(menu); |
||||||
|
} |
||||||
|
|
||||||
|
function getWindowBoundsOptions(): BrowserWindowConstructorOptions { |
||||||
|
// We want the default window behaivour to apply in game mode since it expects everything to be fullscreen and maximized.
|
||||||
|
if (isDeckGameMode) return {}; |
||||||
|
|
||||||
|
const { x, y, width, height } = State.store.windowBounds ?? {}; |
||||||
|
|
||||||
|
const options = { |
||||||
|
width: width ?? DEFAULT_WIDTH, |
||||||
|
height: height ?? DEFAULT_HEIGHT |
||||||
|
} as BrowserWindowConstructorOptions; |
||||||
|
|
||||||
|
if (x != null && y != null) { |
||||||
|
options.x = x; |
||||||
|
options.y = y; |
||||||
|
} |
||||||
|
|
||||||
|
if (!Settings.store.disableMinSize) { |
||||||
|
options.minWidth = MIN_WIDTH; |
||||||
|
options.minHeight = MIN_HEIGHT; |
||||||
|
} |
||||||
|
|
||||||
|
return options; |
||||||
|
} |
||||||
|
|
||||||
|
function getDarwinOptions(): BrowserWindowConstructorOptions { |
||||||
|
const options = { |
||||||
|
titleBarStyle: "hidden", |
||||||
|
trafficLightPosition: { x: 10, y: 10 } |
||||||
|
} as BrowserWindowConstructorOptions; |
||||||
|
|
||||||
|
const { splashTheming, splashBackground } = Settings.store; |
||||||
|
const { macosTranslucency } = VencordSettings.store; |
||||||
|
|
||||||
|
if (macosTranslucency) { |
||||||
|
options.vibrancy = "sidebar"; |
||||||
|
options.backgroundColor = "#ffffff00"; |
||||||
|
} else { |
||||||
|
if (splashTheming) { |
||||||
|
options.backgroundColor = splashBackground; |
||||||
|
} else { |
||||||
|
options.backgroundColor = nativeTheme.shouldUseDarkColors ? "#313338" : "#ffffff"; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return options; |
||||||
|
} |
||||||
|
|
||||||
|
function initWindowBoundsListeners(win: BrowserWindow) { |
||||||
|
const saveState = () => { |
||||||
|
State.store.maximized = win.isMaximized(); |
||||||
|
State.store.minimized = win.isMinimized(); |
||||||
|
}; |
||||||
|
|
||||||
|
win.on("maximize", saveState); |
||||||
|
win.on("minimize", saveState); |
||||||
|
win.on("unmaximize", saveState); |
||||||
|
|
||||||
|
const saveBounds = () => { |
||||||
|
State.store.windowBounds = win.getBounds(); |
||||||
|
}; |
||||||
|
|
||||||
|
win.on("resize", saveBounds); |
||||||
|
win.on("move", saveBounds); |
||||||
|
} |
||||||
|
|
||||||
|
function initSettingsListeners(win: BrowserWindow) { |
||||||
|
addSettingsListener("tray", enable => { |
||||||
|
if (enable) initTray(win); |
||||||
|
else tray?.destroy(); |
||||||
|
}); |
||||||
|
addSettingsListener("disableMinSize", disable => { |
||||||
|
if (disable) { |
||||||
|
// 0 no work
|
||||||
|
win.setMinimumSize(1, 1); |
||||||
|
} else { |
||||||
|
win.setMinimumSize(MIN_WIDTH, MIN_HEIGHT); |
||||||
|
|
||||||
|
const { width, height } = win.getBounds(); |
||||||
|
win.setBounds({ |
||||||
|
width: Math.max(width, MIN_WIDTH), |
||||||
|
height: Math.max(height, MIN_HEIGHT) |
||||||
|
}); |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
addVencordSettingsListener("macosTranslucency", enabled => { |
||||||
|
if (enabled) { |
||||||
|
win.setVibrancy("sidebar"); |
||||||
|
win.setBackgroundColor("#ffffff00"); |
||||||
|
} else { |
||||||
|
win.setVibrancy(null); |
||||||
|
win.setBackgroundColor("#ffffff"); |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
addSettingsListener("enableMenu", enabled => { |
||||||
|
win.setAutoHideMenuBar(enabled ?? false); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
function initSpellCheck(win: BrowserWindow) { |
||||||
|
win.webContents.on("context-menu", (_, data) => { |
||||||
|
win.webContents.send(IpcEvents.SPELLCHECK_RESULT, data.misspelledWord, data.dictionarySuggestions); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
function createMainWindow() { |
||||||
|
// Clear up previous settings listeners
|
||||||
|
removeSettingsListeners(); |
||||||
|
removeVencordSettingsListeners(); |
||||||
|
|
||||||
|
const { staticTitle, transparencyOption, enableMenu, customTitleBar } = Settings.store; |
||||||
|
|
||||||
|
const { frameless, transparent } = VencordSettings.store; |
||||||
|
|
||||||
|
const noFrame = frameless === true || customTitleBar === true; |
||||||
|
|
||||||
|
const win = (mainWin = new BrowserWindow({ |
||||||
|
show: false, |
||||||
|
webPreferences: { |
||||||
|
nodeIntegration: false, |
||||||
|
sandbox: false, |
||||||
|
contextIsolation: true, |
||||||
|
devTools: true, |
||||||
|
preload: join(__dirname, "preload.js"), |
||||||
|
spellcheck: true |
||||||
|
}, |
||||||
|
icon: ICON_PATH, |
||||||
|
frame: !noFrame, |
||||||
|
...(transparent && { |
||||||
|
transparent: true, |
||||||
|
backgroundColor: "#00000000" |
||||||
|
}), |
||||||
|
...(transparencyOption && |
||||||
|
transparencyOption !== "none" && { |
||||||
|
backgroundColor: "#00000000", |
||||||
|
backgroundMaterial: transparencyOption |
||||||
|
}), |
||||||
|
// Fix transparencyOption for custom discord titlebar
|
||||||
|
...(customTitleBar && |
||||||
|
transparencyOption && |
||||||
|
transparencyOption !== "none" && { |
||||||
|
transparent: true |
||||||
|
}), |
||||||
|
...(staticTitle && { title: "Vesktop" }), |
||||||
|
...(process.platform === "darwin" && getDarwinOptions()), |
||||||
|
...getWindowBoundsOptions(), |
||||||
|
autoHideMenuBar: enableMenu |
||||||
|
})); |
||||||
|
win.setMenuBarVisibility(false); |
||||||
|
|
||||||
|
win.on("close", e => { |
||||||
|
const useTray = !isDeckGameMode && Settings.store.minimizeToTray !== false && Settings.store.tray !== false; |
||||||
|
if (isQuitting || (process.platform !== "darwin" && !useTray)) return; |
||||||
|
|
||||||
|
e.preventDefault(); |
||||||
|
|
||||||
|
if (process.platform === "darwin") app.hide(); |
||||||
|
else win.hide(); |
||||||
|
|
||||||
|
return false; |
||||||
|
}); |
||||||
|
|
||||||
|
if (Settings.store.staticTitle) win.on("page-title-updated", e => e.preventDefault()); |
||||||
|
|
||||||
|
initWindowBoundsListeners(win); |
||||||
|
if (!isDeckGameMode && (Settings.store.tray ?? true) && process.platform !== "darwin") initTray(win); |
||||||
|
initMenuBar(win); |
||||||
|
makeLinksOpenExternally(win); |
||||||
|
initSettingsListeners(win); |
||||||
|
initSpellCheck(win); |
||||||
|
|
||||||
|
win.webContents.setUserAgent(BrowserUserAgent); |
||||||
|
|
||||||
|
const subdomain = |
||||||
|
Settings.store.discordBranch === "canary" || Settings.store.discordBranch === "ptb" |
||||||
|
? `${Settings.store.discordBranch}.` |
||||||
|
: ""; |
||||||
|
|
||||||
|
win.loadURL(`https://${subdomain}discord.com/app`); |
||||||
|
|
||||||
|
return win; |
||||||
|
} |
||||||
|
|
||||||
|
const runVencordMain = once(() => require(join(VENCORD_FILES_DIR, "vencordDesktopMain.js"))); |
||||||
|
|
||||||
|
export async function createWindows() { |
||||||
|
const startMinimized = process.argv.includes("--start-minimized"); |
||||||
|
const splash = createSplashWindow(startMinimized); |
||||||
|
// SteamOS letterboxes and scales it terribly, so just full screen it
|
||||||
|
if (isDeckGameMode) splash.setFullScreen(true); |
||||||
|
await ensureVencordFiles(); |
||||||
|
runVencordMain(); |
||||||
|
|
||||||
|
mainWin = createMainWindow(); |
||||||
|
|
||||||
|
mainWin.webContents.on("did-finish-load", () => { |
||||||
|
splash.destroy(); |
||||||
|
|
||||||
|
if (!startMinimized) { |
||||||
|
mainWin!.show(); |
||||||
|
if (State.store.maximized && !isDeckGameMode) mainWin!.maximize(); |
||||||
|
} |
||||||
|
|
||||||
|
if (isDeckGameMode) { |
||||||
|
// always use entire display
|
||||||
|
mainWin!.setFullScreen(true); |
||||||
|
|
||||||
|
askToApplySteamLayout(mainWin); |
||||||
|
} |
||||||
|
|
||||||
|
mainWin.once("show", () => { |
||||||
|
if (State.store.maximized && !mainWin!.isMaximized() && !isDeckGameMode) { |
||||||
|
mainWin!.maximize(); |
||||||
|
} |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
initArRPC(); |
||||||
|
} |
@ -0,0 +1,24 @@ |
|||||||
|
/* |
||||||
|
* SPDX-License-Identifier: GPL-3.0 |
||||||
|
* Aerocord, a vesktop fork for older microsoft NT releases such as NT 6.0, 6.1, 6.2 and 6.3.
|
||||||
|
* Credits to vendicated and the rest of the vesktop contribuitors for making Vesktop! |
||||||
|
*/ |
||||||
|
|
||||||
|
import { session, systemPreferences } from "electron"; |
||||||
|
|
||||||
|
export function registerMediaPermissionsHandler() { |
||||||
|
if (process.platform !== "darwin") return; |
||||||
|
|
||||||
|
session.defaultSession.setPermissionRequestHandler(async (_webContents, permission, callback, details) => { |
||||||
|
let granted = true; |
||||||
|
|
||||||
|
if (details.mediaTypes?.includes("audio")) { |
||||||
|
granted = await systemPreferences.askForMediaAccess("microphone"); |
||||||
|
} |
||||||
|
if (details.mediaTypes?.includes("video")) { |
||||||
|
granted &&= await systemPreferences.askForMediaAccess("camera"); |
||||||
|
} |
||||||
|
|
||||||
|
callback(granted); |
||||||
|
}); |
||||||
|
} |
Loading…
Reference in new issue