port popout logic from discord desktop

This commit is contained in:
Vendicated 2024-01-15 18:52:46 +01:00
parent 38f0330eb2
commit 6483b3a3d9
No known key found for this signature in database
GPG key ID: D66986BAF75ECF18
5 changed files with 172 additions and 41 deletions

View file

@ -22,7 +22,13 @@
"eqeqeq": ["error", "always", { "null": "ignore" }], "eqeqeq": ["error", "always", { "null": "ignore" }],
"spaced-comment": ["error", "always", { "markers": ["!"] }], "spaced-comment": ["error", "always", { "markers": ["!"] }],
"yoda": "error", "yoda": "error",
"prefer-destructuring": ["error", { "object": true, "array": false }], "prefer-destructuring": [
"error",
{
"VariableDeclarator": { "array": false, "object": true },
"AssignmentExpression": { "array": false, "object": false }
}
],
"operator-assignment": ["error", "always"], "operator-assignment": ["error", "always"],
"no-useless-computed-key": "error", "no-useless-computed-key": "error",
"no-unneeded-ternary": ["error", { "defaultAssignment": false }], "no-unneeded-ternary": ["error", { "defaultAssignment": false }],

View file

@ -42,6 +42,8 @@ export const MIN_HEIGHT = 500;
export const DEFAULT_WIDTH = 1280; export const DEFAULT_WIDTH = 1280;
export const DEFAULT_HEIGHT = 720; export const DEFAULT_HEIGHT = 720;
export const DISCORD_HOSTNAMES = ["discord.com", "canary.discord.com", "ptb.discord.com"];
const UserAgents = { const UserAgents = {
darwin: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", darwin: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
linux: "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", linux: "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",

View file

@ -5,20 +5,14 @@
*/ */
import { ipcMain, IpcMainEvent, IpcMainInvokeEvent, WebFrameMain } from "electron"; import { ipcMain, IpcMainEvent, IpcMainInvokeEvent, WebFrameMain } from "electron";
import { DISCORD_HOSTNAMES } from "main/constants";
import { IpcEvents } from "shared/IpcEvents"; import { IpcEvents } from "shared/IpcEvents";
export function validateSender(frame: WebFrameMain) { export function validateSender(frame: WebFrameMain) {
const { hostname, protocol } = new URL(frame.url); const { hostname, protocol } = new URL(frame.url);
if (protocol === "file:") return; if (protocol === "file:") return;
switch (hostname) { if (!DISCORD_HOSTNAMES.includes(hostname)) throw new Error("ipc: Disallowed host " + hostname);
case "discord.com":
case "ptb.discord.com":
case "canary.discord.com":
break;
default:
throw new Error("ipc: Disallowed host " + hostname);
}
} }
export function handleSync(event: IpcEvents, cb: (e: IpcMainEvent, ...args: any[]) => any) { export function handleSync(event: IpcEvents, cb: (e: IpcMainEvent, ...args: any[]) => any) {

View file

@ -5,50 +5,67 @@
*/ */
import { BrowserWindow, shell } from "electron"; import { BrowserWindow, shell } from "electron";
import { DISCORD_HOSTNAMES } from "main/constants";
import { Settings } from "../settings"; import { Settings } from "../settings";
import { createOrFocusPopup, setupPopout } from "./popout";
import { execSteamURL, isDeckGameMode, steamOpenURL } from "./steamOS"; import { execSteamURL, isDeckGameMode, steamOpenURL } from "./steamOS";
const DISCORD_HOSTNAMES = ["discord.com", "canary.discord.com", "ptb.discord.com"]; export function handleExternalUrl(url: string, protocol?: string): { action: "deny" | "allow" } {
if (protocol == null) {
try {
protocol = new URL(url).protocol;
} catch {
return { action: "deny" };
}
}
switch (protocol) {
case "http:":
case "https:":
if (Settings.store.openLinksWithElectron) {
return { action: "allow" };
}
// eslint-disable-next-line no-fallthrough
case "mailto:":
case "spotify:":
if (isDeckGameMode) {
steamOpenURL(url);
} else {
shell.openExternal(url);
}
break;
case "steam:":
if (isDeckGameMode) {
execSteamURL(url);
} else {
shell.openExternal(url);
}
break;
}
return { action: "deny" };
}
export function makeLinksOpenExternally(win: BrowserWindow) { export function makeLinksOpenExternally(win: BrowserWindow) {
win.webContents.setWindowOpenHandler(({ url, frameName }) => { win.webContents.setWindowOpenHandler(({ url, frameName, features }) => {
try { try {
var { protocol, hostname, pathname } = new URL(url); var { protocol, hostname, pathname } = new URL(url);
} catch { } catch {
return { action: "deny" }; return { action: "deny" };
} }
if ( if (frameName.startsWith("DISCORD_") && pathname === "/popout" && DISCORD_HOSTNAMES.includes(hostname)) {
url === "about:blank" || return createOrFocusPopup(frameName, features);
(pathname === "/popout" && DISCORD_HOSTNAMES.includes(hostname)) ||
(frameName === "authorize" && DISCORD_HOSTNAMES.includes(hostname))
)
return { action: "allow" };
switch (protocol) {
case "http:":
case "https:":
if (Settings.store.openLinksWithElectron) {
return { action: "allow" };
}
// eslint-disable-next-line no-fallthrough
case "mailto:":
case "spotify:":
if (isDeckGameMode) {
steamOpenURL(url);
} else {
shell.openExternal(url);
}
break;
case "steam:":
if (isDeckGameMode) {
execSteamURL(url);
} else {
shell.openExternal(url);
}
break;
} }
return { action: "deny" }; if (url === "about:blank" || (frameName === "authorize" && DISCORD_HOSTNAMES.includes(hostname)))
return { action: "allow" };
return handleExternalUrl(url, protocol);
});
win.webContents.on("did-create-window", (win, { frameName }) => {
if (frameName.startsWith("DISCORD_")) setupPopout(win, frameName);
}); });
} }

112
src/main/utils/popout.ts Normal file
View file

@ -0,0 +1,112 @@
/*
* SPDX-License-Identifier: GPL-3.0
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
* Copyright (c) 2023 Vendicated and Vencord contributors
*/
import { BrowserWindow, BrowserWindowConstructorOptions } from "electron";
import { handleExternalUrl } from "./makeLinksOpenExternally";
const ALLOWED_FEATURES = new Set([
"width",
"height",
"left",
"top",
"resizable",
"movable",
"alwaysOnTop",
"frame",
"transparent",
"hasShadow",
"closable",
"skipTaskbar",
"backgroundColor",
"menubar",
"toolbar",
"location",
"directories",
"titleBarStyle"
]);
const MIN_POPOUT_WIDTH = 320;
const MIN_POPOUT_HEIGHT = 180;
const DEFAULT_POPOUT_OPTIONS: BrowserWindowConstructorOptions = {
title: "Discord Popout",
backgroundColor: "#2f3136",
minWidth: MIN_POPOUT_WIDTH,
minHeight: MIN_POPOUT_HEIGHT,
frame: process.platform === "linux",
titleBarStyle: process.platform === "darwin" ? "hidden" : undefined,
trafficLightPosition:
process.platform === "darwin"
? {
x: 10,
y: 3
}
: undefined,
webPreferences: {
nodeIntegration: false,
contextIsolation: true
}
};
const PopoutWindows = new Map<string, BrowserWindow>();
function focusWindow(window: BrowserWindow) {
window.setAlwaysOnTop(true);
window.focus();
window.setAlwaysOnTop(false);
}
function parseFeatureValue(feature: string) {
if (feature === "yes") return true;
if (feature === "no") return false;
const n = Number(feature);
if (!isNaN(n)) return n;
return feature;
}
function parseWindowFeatures(features: string) {
const keyValuesParsed = features.split(",");
return keyValuesParsed.reduce((features, feature) => {
const [key, value] = feature.split("=");
if (ALLOWED_FEATURES.has(key)) features[key] = parseFeatureValue(value);
return features;
}, {});
}
export function createOrFocusPopup(key: string, features: string) {
const existingWindow = PopoutWindows.get(key);
if (existingWindow) {
focusWindow(existingWindow);
return <const>{ action: "deny" };
}
return <const>{
action: "allow",
overrideBrowserWindowOptions: {
...DEFAULT_POPOUT_OPTIONS,
...parseWindowFeatures(features)
}
};
}
export function setupPopout(win: BrowserWindow, key: string) {
PopoutWindows.set(key, win);
/* win.webContents.on("will-navigate", (evt, url) => {
// maybe prevent if not origin match
})*/
win.webContents.setWindowOpenHandler(({ url }) => handleExternalUrl(url));
win.once("closed", () => {
win.removeAllListeners();
PopoutWindows.delete(key);
});
}