port popout logic from discord desktop
This commit is contained in:
parent
38f0330eb2
commit
6483b3a3d9
5 changed files with 172 additions and 41 deletions
|
@ -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 }],
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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
112
src/main/utils/popout.ts
Normal 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);
|
||||||
|
});
|
||||||
|
}
|
Loading…
Reference in a new issue