Add Vencord Loading & tray icon

This commit is contained in:
Vendicated 2023-04-04 00:41:52 +02:00
parent 307051141d
commit f45d6482ac
No known key found for this signature in database
GPG key ID: A1DC0CFB5615D905
12 changed files with 176 additions and 11 deletions

7
src/main/constants.ts Normal file
View file

@ -0,0 +1,7 @@
import { app } from "electron";
import { join } from "path";
export const DATA_DIR = process.env.VENCORD_USER_DATA_DIR ?? join(app.getPath("userData"), "VencordDesktop");
export const VENCORD_FILES_DIR = join(DATA_DIR, "vencordDist");
export const USER_AGENT = `VencordDesktop/${app.getVersion()} (https://github.com/Vencord/Electron)`;

View file

@ -3,21 +3,33 @@ import { createMainWindow } from "./mainWindow";
import { createSplashWindow } from "./splash";
import { join } from "path";
import { DATA_DIR, VENCORD_FILES_DIR } from "./constants";
import { once } from "../shared/utils/once";
import "./ipc";
import { ensureVencordFiles } from "./utils/vencordLoader";
require(join(__dirname, "Vencord/main.js"));
// Make the Vencord files use our DATA_DIR
process.env.VENCORD_USER_DATA_DIR = DATA_DIR;
function createWindows() {
const mainWindow = createMainWindow();
const runVencordMain = once(() => require(join(VENCORD_FILES_DIR, "main.js")));
async function createWindows() {
const splash = createSplashWindow();
await ensureVencordFiles();
runVencordMain();
const mainWindow = createMainWindow();
mainWindow.once("ready-to-show", () => {
splash.destroy();
mainWindow.show();
});
}
app.whenReady().then(() => {
app.whenReady().then(async () => {
createWindows();
app.on('activate', () => {

View file

@ -0,0 +1,8 @@
import { ipcMain } from "electron";
import { join } from "path";
import { GET_PRELOAD_FILE } from "../shared/IpcEvents";
import { VENCORD_FILES_DIR } from "./constants";
ipcMain.on(GET_PRELOAD_FILE, e => {
e.returnValue = join(VENCORD_FILES_DIR, "preload.js");
});

View file

@ -1,7 +1,9 @@
import { BrowserWindow } from "electron";
import { BrowserWindow, Menu, Tray, app } from "electron";
import { join } from "path";
export function createMainWindow() {
let isQuitting = false;
const win = new BrowserWindow({
show: false,
webPreferences: {
@ -10,9 +12,42 @@ export function createMainWindow() {
contextIsolation: true,
devTools: true,
preload: join(__dirname, "preload.js")
}
},
icon: join(__dirname, "..", "..", "static", "icon.ico")
});
app.on("before-quit", () => {
isQuitting = true;
});
win.on("close", e => {
if (isQuitting) return;
e.preventDefault();
win.hide();
return false;
});
const tray = new Tray(join(__dirname, "..", "..", "static", "icon.ico"));
tray.setToolTip("Vencord Desktop");
tray.setContextMenu(Menu.buildFromTemplate([
{
label: "Open",
click() {
win.show();
}
},
{
label: "Quit",
click() {
isQuitting = true;
app.quit();
}
}
]));
tray.on("click", () => win.show());
win.loadURL("https://discord.com/app");
return win;

View file

@ -12,7 +12,7 @@ export function createSplashWindow() {
maximizable: false
});
splash.loadFile(join(__dirname, "..", "static", "splash.html"));
splash.loadFile(join(__dirname, "..", "..", "static", "splash.html"));
return splash;
}

41
src/main/utils/http.ts Normal file
View file

@ -0,0 +1,41 @@
import { createWriteStream } from "fs";
import type { IncomingMessage } from "http";
import { RequestOptions, get } from "https";
import { finished } from "stream/promises";
export async function downloadFile(url: string, file: string, options: RequestOptions = {}) {
const res = await simpleReq(url, options);
await finished(
res.pipe(createWriteStream(file, {
autoClose: true
}))
);
}
export function simpleReq(url: string, options: RequestOptions = {}) {
return new Promise<IncomingMessage>((resolve, reject) => {
get(url, options, res => {
const { statusCode, statusMessage, headers } = res;
if (statusCode! >= 400)
return void reject(`${statusCode}: ${statusMessage} - ${url}`);
if (statusCode! >= 300)
return simpleReq(headers.location!, options)
.then(resolve)
.catch(reject);
resolve(res);
});
});
}
export async function simpleGet(url: string, options: RequestOptions = {}) {
const res = await simpleReq(url, options);
return new Promise<Buffer>((resolve, reject) => {
const chunks = [] as Buffer[];
res.once("error", reject);
res.on("data", chunk => chunks.push(chunk));
res.once("end", () => resolve(Buffer.concat(chunks)));
});
}

View file

@ -0,0 +1,54 @@
import { existsSync, mkdirSync } from "fs";
import { join } from "path";
import { USER_AGENT, VENCORD_FILES_DIR } from "../constants";
import { downloadFile, simpleGet } from "./http";
// TODO: Setting to switch repo
const API_BASE = "https://api.github.com/repos/Vendicated/VencordDev";
const FILES_TO_DOWNLOAD = [
"vencordDesktopMain.js",
"preload.js",
"vencordDesktopRenderer.js",
"renderer.css"
];
export async function githubGet(endpoint: string) {
return simpleGet(API_BASE + endpoint, {
headers: {
Accept: "application/vnd.github+json",
"User-Agent": USER_AGENT
}
});
}
export async function downloadVencordFiles() {
const release = await githubGet("/releases/latest");
const data = JSON.parse(release.toString("utf-8"));
const assets = data.assets as Array<{
name: string;
browser_download_url: string;
}>;
await Promise.all(
assets
.filter(({ name }) => FILES_TO_DOWNLOAD.some(f => name.startsWith(f)))
.map(({ name, browser_download_url }) =>
downloadFile(
browser_download_url,
join(
VENCORD_FILES_DIR,
name.replace(/vencordDesktop(\w)/, (_, c) => c.toLowerCase())
)
)
)
);
}
export async function ensureVencordFiles() {
if (existsSync(join(VENCORD_FILES_DIR, "main.js"))) return;
mkdirSync(VENCORD_FILES_DIR, { recursive: true });
await downloadVencordFiles();
}

View file

@ -1,3 +1,4 @@
import { join } from "path";
import { ipcRenderer } from "electron";
import { GET_PRELOAD_FILE } from "../shared/IpcEvents";
require(join(__dirname, "Vencord/preload.js"));
require(ipcRenderer.sendSync(GET_PRELOAD_FILE));

View file

@ -0,0 +1 @@
export const GET_PRELOAD_FILE = "VCD_GET_PRELOAD_FILE";

View file

@ -1,2 +0,0 @@

8
src/shared/utils/once.ts Normal file
View file

@ -0,0 +1,8 @@
export function once<T extends Function>(fn: T): T {
let called = false;
return function (this: any, ...args: any[]) {
if (called) return;
called = true;
return fn.apply(this, args);
} as any;
}

BIN
static/icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB