Settings, Electron 17 and much more

This commit is contained in:
smartfrigde 2022-02-26 22:26:16 +01:00
parent fcdb247601
commit c98b6016ca
24 changed files with 1226 additions and 244 deletions

633
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -4,7 +4,7 @@
"description": "ArmCord is a custom client designed to enhance your Discord experience while keeping everything lightweight.", "description": "ArmCord is a custom client designed to enhance your Discord experience while keeping everything lightweight.",
"main": "ts-out/main.js", "main": "ts-out/main.js",
"scripts": { "scripts": {
"build": "tsc && copyfiles -u 1 src/**/*.html src/**/*.css ts-out/ && copyfiles package.json ts-out/ && copyfiles assets/** ts-out/", "build": "tsc && copyfiles -u 1 src/**/*.html src/**/**/*.css ts-out/ && copyfiles package.json ts-out/ && copyfiles assets/** ts-out/",
"watch": "tsc -w", "watch": "tsc -w",
"start": "npm run build && electron ./ts-out/main.js", "start": "npm run build && electron ./ts-out/main.js",
"package": "npm run build && electron-builder" "package": "npm run build && electron-builder"
@ -23,12 +23,15 @@
"@types/electron-json-storage": "^4.5.0", "@types/electron-json-storage": "^4.5.0",
"@types/node": "^14.18.2", "@types/node": "^14.18.2",
"copyfiles": "^2.4.1", "copyfiles": "^2.4.1",
"electron": "^16.0.7", "electron": "^17.1.0",
"electron-builder": "^22.14.5", "electron-builder": "^22.14.13",
"typescript": "^4.5.4" "typescript": "^4.5.4"
}, },
"dependencies": { "dependencies": {
"electron-context-menu": "^3.1.2",
"electron-json-storage": "^4.5.0", "electron-json-storage": "^4.5.0",
"electron-tabs": "^0.15.0",
"glasstron": "^0.1.1",
"v8-compile-cache": "^2.3.0" "v8-compile-cache": "^2.3.0"
}, },
"build": { "build": {

View file

@ -1,4 +1,4 @@
.info-1sUqUG:last-child:before { .info-3pQQBb:last-child:before {
content: "ArmCord Version: 3.1.0"!important; content: "ArmCord Version: 3.1.0"!important;
height: auto; height: auto;
line-height: 16px; line-height: 16px;

53
src/content/css/tabs.css Normal file
View file

@ -0,0 +1,53 @@
@import url("https://kckarnige.github.io/femboi_owo/discord-font.css");
:root {
--window-buttons: var(--header-secondary);
--cord-color: var(--header-primary);
--armcord-color: #7289da;
--titlebar-color: var(--background-tertiary);
}
.tabs {
display: block;
top: 0;
left: 0;
right: 0;
flex-shrink: 0;
overflow: hidden;
zoom: 1;
box-sizing: border-box;
width: 100%;
clear: both;
height: 30px;
line-height: 30px;
background-color: #202225;
-webkit-app-region: drag;
width: 100%;
user-select: none;
-webkit-user-select: none;
position: fixed;
z-index: 99999;
}
.tabs #tabs-controls-container {
float: left;
width: 150px;
height: 100%;
line-height: 30px;
background-color: #202225;
-webkit-app-region: no-drag;
}
.tabs-buttons {
color: white;
background-color: inherit;
float: left;
border: none;
outline: none;
cursor: pointer;
transition: 0.3s;
font-size: 20px;
}
.tabs-buttons:hover {
background-color: #4e515a;
}
.withFrame-haYltI {
height: 30px !important;
}

View file

@ -1,4 +1,4 @@
@import url("https://kckarnige.github.io/femboi_owo/discord-font.css"); @import url("https://armcord.smartfridge.space/logofont.css");
:root { :root {
--window-buttons: var(--header-secondary); --window-buttons: var(--header-secondary);
--cord-color: var(--header-primary); --cord-color: var(--header-primary);

View file

@ -1 +0,0 @@
<h1>settings, really cool</h1>

View file

@ -1,3 +1,4 @@
<!--- This is awful and should be replaced in later versions. Possibly based of current settings as of 3.1.0 version. If you have time please PR a better setup screen. --->
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
@ -42,7 +43,7 @@
} else { } else {
console.log("Starting ArmCord Setup..."); console.log("Starting ArmCord Setup...");
document.getElementById("express").addEventListener("click", function () { document.getElementById("express").addEventListener("click", function () {
window.armcordinternal.saveSettings(true, "stable", true, "cumcord"); window.armcordinternal.saveSettings("default", "stable", true, true, false, "cumcord", "acrylic");
fade(document.getElementById("setup")); fade(document.getElementById("setup"));
setTimeout(function () { setTimeout(function () {
window.armcordinternal.restart() window.armcordinternal.restart()
@ -90,15 +91,15 @@
.getElementById("next") .getElementById("next")
.addEventListener("click", function () { .addEventListener("click", function () {
var mod = document.getElementById("mod").value; var mod = document.getElementById("mod").value;
window.armcordinternal.saveSettings(true, branch, true, mod); window.armcordinternal.saveSettings("default", branch, true,true,false, mod, "acrylic");
fade(document.getElementById("setup")); fade(document.getElementById("setup"));
setTimeout(function () { setTimeout(function () {
window.armcordinternal.restart(); window.armcordinternal.restart();
}, 5000); }, 5000);
}); });
} else { } else {
//saveSettings(customTitlebarSetting: boolean, channelSetting: string, armcordCSPSetting: boolean, modsSetting: string) //saveSettings(windowStyle: string, channelSetting: string, armcordCSPSetting: boolean, minimizeToTray:boolean, modsSetting: string)
window.armcordinternal.saveSettings(true, branch, true, "none"); window.armcordinternal.saveSettings("default", branch, true,true,false, "none", "acrylic");
fade(document.getElementById("setup")); fade(document.getElementById("setup"));
setTimeout(function () { setTimeout(function () {
window.armcordinternal.restart() window.armcordinternal.restart()

View file

@ -1,13 +1,16 @@
//ipc stuff //ipc stuff
import { app, ipcMain, shell, desktopCapturer } from "electron"; import { app, ipcMain, shell, desktopCapturer } from "electron";
import { mainWindow } from "./window"; import { createTabsGuest, mainWindow } from "./window";
import { saveSettings, getVersion } from "./utils"; import { saveSettings, getVersion } from "./utils";
import { settings, customTitlebar } from "./main"; import { settings, customTitlebar, tabs } from "./main";
import { createSettingsWindow } from "./settings/main"; import { createSettingsWindow } from "./settings/main";
export function registerIpc() { export function registerIpc() {
ipcMain.on("get-app-path", (event, arg) => { ipcMain.on("get-app-path", (event, arg) => {
event.reply("app-path", app.getAppPath()); event.reply("app-path", app.getAppPath());
}); });
ipcMain.on("openTab", (event, number: number) => {
createTabsGuest(number);
});
ipcMain.on("open-external-link", (event, href: string) => { ipcMain.on("open-external-link", (event, href: string) => {
shell.openExternal(href); shell.openExternal(href);
}); });
@ -29,6 +32,9 @@ export function registerIpc() {
ipcMain.on("win-hide", (event, arg) => { ipcMain.on("win-hide", (event, arg) => {
mainWindow.hide(); mainWindow.hide();
}); });
ipcMain.on("win-quit", (event, arg) => {
app.exit();
});
ipcMain.on("get-app-version", (event) => { ipcMain.on("get-app-version", (event) => {
event.returnValue = getVersion(); event.returnValue = getVersion();
}); });
@ -39,11 +45,14 @@ export function registerIpc() {
app.relaunch(); app.relaunch();
app.exit(); app.exit();
}); });
ipcMain.on("saveSettings", (event, ...args) => { ipcMain.on("saveSettings", (event, ...args) => {
//@ts-ignore //@ts-ignore
saveSettings(...args); saveSettings(...args);
}); });
ipcMain.on("minimizeToTray", (event) => {
console.log(settings.minimizeToTray);
event.returnValue = settings.minimizeToTray;
});
ipcMain.on("channel", (event) => { ipcMain.on("channel", (event) => {
event.returnValue = settings.channel; event.returnValue = settings.channel;
}); });
@ -53,6 +62,12 @@ export function registerIpc() {
ipcMain.on("titlebar", (event, arg) => { ipcMain.on("titlebar", (event, arg) => {
event.returnValue = customTitlebar; event.returnValue = customTitlebar;
}); });
ipcMain.on("tabs", (event, arg) => {
event.returnValue = tabs;
});
ipcMain.on("shouldPatch", (event, arg) => {
event.returnValue = settings.automaticPatches;
});
ipcMain.on("openSettingsWindow", (event, arg) => { ipcMain.on("openSettingsWindow", (event, arg) => {
createSettingsWindow(); createSettingsWindow();
}); });
@ -66,5 +81,4 @@ export function registerIpc() {
ipcMain.handle("DESKTOP_CAPTURER_GET_SOURCES", (event, opts) => ipcMain.handle("DESKTOP_CAPTURER_GET_SOURCES", (event, opts) =>
desktopCapturer.getSources(opts) desktopCapturer.getSources(opts)
); );
} }

View file

@ -11,30 +11,38 @@ import { getConfigUnsafe, setup } from "./utils";
import "./extensions/mods"; import "./extensions/mods";
import "./extensions/plugin"; import "./extensions/plugin";
import "./tray"; import "./tray";
import { mainWindow, createCustomWindow, createNativeWindow } from "./window"; import { mainWindow, createCustomWindow, createNativeWindow, createGlasstronWindow, createTabsHost } from "./window";
import "./shortcuts"; import "./shortcuts";
export var contentPath: string; export var contentPath: string;
var channel: string; var channel: string;
export var settings: any; export var settings: any;
export var customTitlebar: boolean; export var customTitlebar: boolean;
storage.has("settings", function (error, hasKey) { export var tabs: boolean;
if (error) throw error; async function appendSwitch(){
if (await getConfigUnsafe("windowStyle") == "glasstron") {
if (!hasKey) { console.log("Enabling transparency visuals.");
console.log("First run of the ArmCord. Starting setup."); app.commandLine.appendSwitch("enable-transparent-visuals");
setup();
contentPath = path.join(__dirname, "/content/setup.html");
if (!contentPath.includes("ts-out")) {
contentPath = path.join(__dirname, "/ts-out/content/setup.html");
}
} else {
console.log("ArmCord has been run before. Skipping setup.");
contentPath = path.join(__dirname, "/content/splash.html");
if (!contentPath.includes("ts-out")) {
contentPath = path.join(__dirname, "/ts-out/content/splash.html");
}
} }
}); }
appendSwitch();
storage.has("settings", function (error, hasKey) {
if (error) throw error;
if (!hasKey) {
console.log("First run of the ArmCord. Starting setup.");
setup();
contentPath = path.join(__dirname, "/content/setup.html");
if (!contentPath.includes("ts-out")) {
contentPath = path.join(__dirname, "/ts-out/content/setup.html");
}
} else {
console.log("ArmCord has been run before. Skipping setup.");
contentPath = path.join(__dirname, "/content/splash.html");
if (!contentPath.includes("ts-out")) {
contentPath = path.join(__dirname, "/ts-out/content/splash.html");
}
}
});
storage.get("settings", function (error, data: any) { storage.get("settings", function (error, data: any) {
if (error) throw error; if (error) throw error;
console.log(data); console.log(data);
@ -42,19 +50,33 @@ storage.get("settings", function (error, data: any) {
settings = data; settings = data;
}); });
app.whenReady().then(async () => { app.whenReady().then(async () => {
if (await getConfigUnsafe("customTitlebar") == true) { switch (await getConfigUnsafe("windowStyle")) {
console.log("Creating custom titlebar window."); case "default":
customTitlebar = true; createCustomWindow();
createCustomWindow(); customTitlebar = true;
} else if (await getConfigUnsafe("customTitlebar") == "setup") { break;
//rare case of setup window case "native":
console.log("Creating setup window."); createNativeWindow();
customTitlebar = true; break;
createCustomWindow(); case "glasstron":
} else { setTimeout(
console.log("Creating native titlebar window."); createGlasstronWindow,
customTitlebar = false; process.platform == "linux" ? 1000 : 0
createNativeWindow(); // Electron has a bug on linux where it
// won't initialize properly when using
// transparency. To work around that, it
// is necessary to delay the window
// spawn function.
);
break;
case "tabs":
createTabsHost();
tabs = true;
break;
default:
createCustomWindow();
customTitlebar = true;
break;
} }
session session
.fromPartition("some-partition") .fromPartition("some-partition")
@ -68,19 +90,21 @@ app.whenReady().then(async () => {
callback(true); callback(true);
} }
}); });
mainWindow.webContents.session.webRequest.onBeforeRequest( app.on("activate", async function () {
(details, callback) => {
if (/api\/v\d\/science$/g.test(details.url))
return callback({ cancel: true });
return callback({});
}
);
app.on("activate", function () {
if (BrowserWindow.getAllWindows().length === 0) if (BrowserWindow.getAllWindows().length === 0)
if (!settings.customTitlebar) { switch (await getConfigUnsafe("windowStyle")) {
createNativeWindow(); case "default":
} else { createCustomWindow();
createCustomWindow(); break;
case "native":
createNativeWindow();
break;
case "glasstron":
createGlasstronWindow();
break;
default:
createCustomWindow();
break;
} }
}); });
}); });

View file

@ -15,6 +15,7 @@ contextBridge.exposeInMainWorld("armcord", {
}, },
electron: process.versions.electron, electron: process.versions.electron,
channel: ipcRenderer.sendSync("channel"), channel: ipcRenderer.sendSync("channel"),
openTab: (number: number) => ipcRenderer.sendSync("openTab", number),
version: ipcRenderer.sendSync("get-app-version", "app-version"), version: ipcRenderer.sendSync("get-app-version", "app-version"),
getDisplayMediaSelector: getDisplayMediaSelector, getDisplayMediaSelector: getDisplayMediaSelector,
openSettingsWindow: () => ipcRenderer.send("openSettingsWindow"), openSettingsWindow: () => ipcRenderer.send("openSettingsWindow"),

View file

@ -1,8 +1,11 @@
//Fixed context isolation version https://github.com/getferdi/ferdi/blob/develop/src/webview/screenshare.ts //Fixed context isolation version https://github.com/getferdi/ferdi/blob/develop/src/webview/screenshare.ts
//original https://github.com/electron/electron/issues/16513#issuecomment-602070250 //original https://github.com/electron/electron/issues/16513#issuecomment-602070250
import { desktopCapturer } from 'electron'; import { ipcRenderer } from 'electron';
import {addStyle, addScript} from '../utils'; import {addStyle, addScript} from '../utils';
const desktopCapturer = {
getSources: (opts: any) =>
ipcRenderer.invoke("DESKTOP_CAPTURER_GET_SOURCES", opts),
};
const CANCEL_ID = 'desktop-capturer-selection__cancel'; const CANCEL_ID = 'desktop-capturer-selection__cancel';
export async function getDisplayMediaSelector() { export async function getDisplayMediaSelector() {
@ -12,7 +15,7 @@ export async function getDisplayMediaSelector() {
return `<div class="desktop-capturer-selection__scroller"> return `<div class="desktop-capturer-selection__scroller">
<ul class="desktop-capturer-selection__list"> <ul class="desktop-capturer-selection__list">
${sources ${sources
.map( .map( //@ts-ignore i know it works
({ id, name, thumbnail }) => ` ({ id, name, thumbnail }) => `
<li class="desktop-capturer-selection__item"> <li class="desktop-capturer-selection__item">
<button class="desktop-capturer-selection__btn" data-id="${id}" title="${name}"> <button class="desktop-capturer-selection__btn" data-id="${id}" title="${name}">

22
src/preload/patch.ts Normal file
View file

@ -0,0 +1,22 @@
// What does this do?
// In case of faulty update of ArmCord we can quickly push an update to the user and possibly try to fix it
// This is completely optional and is disabled by default in settings
import { ipcRenderer } from "electron";
import { injectJS } from "../utils";
var patchEndpoint = "https://patch.armcord.xyz/";
var version = ipcRenderer.sendSync("get-app-version", "app-version");
if (ipcRenderer.sendSync("shouldPatch")) {
document.addEventListener("DOMContentLoaded", function () {
fetch(patchEndpoint + version + "/info.json", {cache: "no-store"}) //lmao
.then((res) => res.json())
.then((res) => {
if (res.patch == true) {
console.log("Found a patch. Injecting...");
injectJS(patchEndpoint + version + "/patch.js");
} else {
console.log("No patches have been found.")
}
});
});
}

View file

@ -1,11 +1,12 @@
import "./bridge"; import "./bridge";
import "./capturer"; import "./capturer";
import "./patch"
import * as fs from "fs"; import * as fs from "fs";
import * as path from "path"; import * as path from "path";
import { injectTitlebar } from "./titlebar"; import { injectTitlebar } from "./titlebar";
import { sleep, addStyle } from "../utils"; import { sleep, addStyle, injectJS } from "../utils";
import { ipcRenderer } from "electron"; import { ipcRenderer } from "electron";
import { injectTabs } from "./tabs";
declare global { declare global {
interface Window { interface Window {
armcord: any; armcord: any;
@ -17,15 +18,6 @@ const clientMods = {
"https://raw.githubusercontent.com/Cumcord/Cumcord/stable/dist/build.js", "https://raw.githubusercontent.com/Cumcord/Cumcord/stable/dist/build.js",
flicker: "https://raw.githubusercontent.com/FlickerMod/dist/main/build.js", flicker: "https://raw.githubusercontent.com/FlickerMod/dist/main/build.js",
}; };
async function injectJS(inject: string) {
const js = await (await fetch(`${inject}`)).text();
const el = document.createElement("script");
el.appendChild(document.createTextNode(js));
document.body.appendChild(el);
}
console.log("ArmCord"); console.log("ArmCord");
if (window.location.href.indexOf("splash.html") > -1) { if (window.location.href.indexOf("splash.html") > -1) {
@ -34,6 +26,9 @@ if (window.location.href.indexOf("splash.html") > -1) {
if (ipcRenderer.sendSync("titlebar")) { if (ipcRenderer.sendSync("titlebar")) {
injectTitlebar(); injectTitlebar();
} }
if (ipcRenderer.sendSync("tabs")) {
injectTabs();
}
sleep(5000).then(() => { sleep(5000).then(() => {
const cssPath = path.join(__dirname, "../", "/content/css/discord.css"); const cssPath = path.join(__dirname, "../", "/content/css/discord.css");
addStyle(fs.readFileSync(cssPath, "utf8")); addStyle(fs.readFileSync(cssPath, "utf8"));

25
src/preload/tabs.ts Normal file
View file

@ -0,0 +1,25 @@
import { addStyle } from "../utils";
import * as fs from "fs";
import * as path from "path";
export function injectTabs() {
document.addEventListener("DOMContentLoaded", function (event) {
var elem = document.createElement("div");
elem.innerHTML = `<nav class="tabs">
<div id="tabs-controls-container">
<button class="tabs-buttons" onclick="armcord.openTab(1)">1</button>
<button class="tabs-buttons" onclick="armcord.openTab(2)">2</button>
<button class="tabs-buttons" onclick="armcord.openTab(3)">3</button>
<button class="tabs-buttons" onclick="armcord.openTab(4)">4</button>
<button class="tabs-buttons" onclick="armcord.openTab(5)">5</button>
<p class="experimental">Experimental</p>
</div>
</nav>`;
elem.classList.add("withFrame-haYltI");
if (document.getElementById("app-mount") == null) {
document.body.appendChild(elem);
} else {
document.getElementById("app-mount")!.prepend(elem);
}
const cssPath = path.join(__dirname, "../", "/content/css/tabs.css");
addStyle(fs.readFileSync(cssPath, "utf8"));
})}

View file

@ -39,7 +39,11 @@ export function injectTitlebar() {
}); });
quit!.addEventListener("click", () => { quit!.addEventListener("click", () => {
ipcRenderer.send("win-hide"); if (ipcRenderer.sendSync("minimizeToTray") === true) {
ipcRenderer.send("win-hide");
} else if (ipcRenderer.sendSync("minimizeToTray") === false) {
ipcRenderer.send("win-quit");
}
}); });
}); });
} }

View file

@ -1,25 +1,46 @@
import { BrowserWindow, shell } from "electron"; import { BrowserWindow, shell, ipcMain } from "electron";
import * as storage from "electron-json-storage";
import {getConfigUnsafe, saveSettings} from "../utils";
import path from "path"; import path from "path";
var settings:any;
var settingsWindow; var isAlreadyCreated:boolean = false;
storage.get("settings", function (error, data: any) {
if (error) throw error;
console.log(data);
settings = data;
});
var settingsWindow:BrowserWindow;
export function createSettingsWindow() { export function createSettingsWindow() {
if (isAlreadyCreated) {
settingsWindow.show();
} else {
settingsWindow = new BrowserWindow({ settingsWindow = new BrowserWindow({
width: 500, width: 500,
height: 600, height: 500,
title: "ArmCord Settings", title: "ArmCord Settings",
darkTheme: true, darkTheme: true,
icon: path.join(__dirname, "/assets/icon_transparent.png"), frame: true,
frame: false,
autoHideMenuBar: true, autoHideMenuBar: true,
webPreferences: { webPreferences: {
preload: path.join(__dirname, "preload/preload.js"), preload: path.join(__dirname, "preload.js"),
}, },
}); });
ipcMain.on("saveSettings", (event, ...args) => {
//@ts-ignore
saveSettings(...args);
});
ipcMain.handle("getSetting", (event, toGet: string) => {
return getConfigUnsafe(toGet);
});
settingsWindow.webContents.setWindowOpenHandler(({ url }) => { settingsWindow.webContents.setWindowOpenHandler(({ url }) => {
shell.openExternal(url); shell.openExternal(url);
return { action: "deny" }; return { action: "deny" };
}); });
settingsWindow.loadURL(`file://${__dirname}/settings.html`);
settingsWindow.loadFile("settings.html"); settingsWindow.on("close", async (e) => {
e.preventDefault()
settingsWindow.hide()
});
isAlreadyCreated = true;
}
} }

View file

@ -1 +1,6 @@
console.log("test") import {contextBridge, ipcRenderer} from "electron";
console.log("ArmCord Settings")
contextBridge.exposeInMainWorld("settings", {
save: (...args: any) => ipcRenderer.send("saveSettings", ...args),
get: (toGet: string) => ipcRenderer.invoke('getSetting', toGet).then((result) => {return result}) //jank but works
});

157
src/settings/settings.css Normal file
View file

@ -0,0 +1,157 @@
/*MIT License
Copyright (c) 2021 GooseMod
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.*/
:root {
--background-primary: #282b30;
--background-secondary: rgba(255, 255, 255, 0.1);
--brand-experiment: #5865f2;
--header-primary: #fff;
--text-muted: #72767d;
}
@font-face {
font-family: Whitney;
font-weight: 400;
font-style: normal;
src: url(https://armcord.smartfridge.space/whitney_400.woff) format("woff");
}
html,
body {
overflow: hidden;
margin: 0;
padding: 0;
margin: 2%;
background: var(--background-primary);
}
* {
font-family: "Whitney", sans-serif;
box-sizing: border-box;
cursor: default;
}
.left {
float:right;
vertical-align: right!important;
}
.switch {
vertical-align: middle;
}
.header {
color: white;
font-size: 1.5em;
}
.center {
text-align: center;
}
/*buttons*/
button {
background-color: #7289da;
font-family: Whitney, "Helvetica Neue", Helvetica, Arial, sans-serif;
color: #ffffff;
padding: 4px;
border-radius: 5px;
margin-top: 5px;
text-align: center;
border-style: none;
outline: none;
}
button:hover {
background-color: #687dc6;
border-style: none;
outline: none;
cursor: pointer;
}
.tgl {
display: none;
}
.tgl, .tgl:after, .tgl:before, .tgl *, .tgl *:after, .tgl *:before, .tgl + .tgl-btn {
box-sizing: border-box;
}
.tgl::-moz-selection, .tgl:after::-moz-selection, .tgl:before::-moz-selection, .tgl *::-moz-selection, .tgl *:after::-moz-selection, .tgl *:before::-moz-selection, .tgl + .tgl-btn::-moz-selection {
background: none;
}
.tgl::selection, .tgl:after::selection, .tgl:before::selection, .tgl *::selection, .tgl *:after::selection, .tgl *:before::selection, .tgl + .tgl-btn::selection {
background: none;
}
.tgl + .tgl-btn {
outline: 0;
display: block;
width: 4em;
height: 2em;
position: relative;
cursor: pointer;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.tgl + .tgl-btn:after, .tgl + .tgl-btn:before {
position: relative;
display: block;
content: "";
width: 50%;
height: 100%;
}
.tgl + .tgl-btn:after {
left: 0;
}
.tgl + .tgl-btn:before {
display: none;
}
.tgl:checked + .tgl-btn:after {
left: 50%;
}
.tgl-light + .tgl-btn {
background: #5c5757;
border-radius: 2em;
padding: 2px;
transition: all 0.4s ease;
}
.tgl-light + .tgl-btn:after {
border-radius: 50%;
background: rgb(255, 255, 255);
transition: all 0.2s ease;
}
.tgl-light:checked + .tgl-btn {
background: #47ca5a;
}
select {
-webkit-appearance: button;
-moz-appearance: button;
background-color: #2c2f33;
background-position: center right;
background-repeat: no-repeat;
border: 1px solid #aaa;
border-radius: 2px;
box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.1);
color: #fff;
font-size: 1.2em;
margin: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}

View file

@ -0,0 +1,88 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>ArmCord Settings</title>
<style>
@import url("settings.css");
</style>
</head>
<body>
<div class="switch">
<select name="theme" id="theme" class="left">
<option value="default">Default</option>
<option value="native">Native</option>
<option value="glasstron">Glasstron (experimental)</option>
<option value="tabs">Tabs (experimental)</option>
</select>
<p class="header">ArmCord theme:</p>
</div>
</br>
<div class="switch">
<label class="header">ArmCord CSP</label>
<input class="tgl tgl-light left" id="csp" type="checkbox" />
<label class="tgl-btn left" for="csp"></label>
</div>
</br>
<div class="switch">
<label class="header">Minimize to tray</label>
<input class="tgl tgl-light left" id="tray" type="checkbox" />
<label class="tgl-btn left" for="tray"></label>
</div>
</br>
<div class="switch">
<label class="header">Automatic Patches</label>
<input class="tgl tgl-light left" id="patches" type="checkbox" />
<label class="tgl-btn left" for="patches"></label>
</div>
<div class="switch">
<select name="channel" id="channel" class="left">
<option value="stable">Stable</option>
<option value="canary">Canary</option>
<option value="ptb">PTB</option>
<option value="foss">Fosscord</option>
</select>
<p class="header">Discord channel:</p>
</div>
<div class="switch">
<select name="mod" id="mod" class="left">
<option value="cumcord">Cumcord</option>
<option value="goosemod">GooseMod</option>
<option value="flicker">Flicker</option>
</select>
<p class="header">Client mod:</p>
</div>
<div class="switch">
<select name="blurType" id="blurType" class="left">
<option value="acrylic">Acrylic</option>
<option value="blurbehind">Blur Behind</option>
<option value="transparent">Transparent</option>
<option value="none">None</option>
</select>
<p class="header">Glasstron blur type:</p>
</div>
<button id="save" class="center">Save settings</button>
</body>
<script>
async function loadSettings() {
document.getElementById("csp").checked = await settings.get("armcordCSP");
document.getElementById("tray").checked = await settings.get("minimizeToTray");
document.getElementById("patches").value = await settings.get("automaticPatches");
document.getElementById("mod").value = await settings.get("mods");
document.getElementById("channel").value = await settings.get("channel");
document.getElementById("theme").value = await settings.get("windowStyle");
document.getElementById("blurType").value = await settings.get("blurType");
}
loadSettings()
document.getElementById("save").addEventListener("click", function() {
//function saveSettings(windowStyle: string, channelSetting: string, armcordCSPSetting: boolean, minimizeToTray: boolean, automaticPatches: boolean,modsSetting: string, blurType: string)
settings.save(document.getElementById("theme").value, document.getElementById("channel").value, document.getElementById("csp").checked, document.getElementById("tray").checked, document.getElementById("patches").checked, document.getElementById("mod").value, document.getElementById("blurType").value);
});
</script>
</html>

View file

@ -1,6 +1,7 @@
import { app, Menu, Tray } from 'electron'; import { app, Menu, Tray } from 'electron';
import {mainWindow} from './window'; import {mainWindow} from './window';
import * as path from 'path' import * as path from 'path'
import { createSettingsWindow } from './settings/main';
let tray = null let tray = null
app.whenReady().then(() => { app.whenReady().then(() => {
tray = new Tray(path.join(__dirname, "../", "/assets/ac_plug.png")) tray = new Tray(path.join(__dirname, "../", "/assets/ac_plug.png"))
@ -11,6 +12,12 @@ app.whenReady().then(() => {
mainWindow.show(); mainWindow.show();
}, },
}, },
{
label: "Open Settings",
click: function () {
createSettingsWindow();
},
},
{ {
label: "Support Discord Server", label: "Support Discord Server",
click: function () { click: function () {

86
src/types/glasstron.d.ts vendored Normal file
View file

@ -0,0 +1,86 @@
declare module "glasstron" {
export class BrowserWindow extends Electron.BrowserWindow {
getBlur(): Promise<boolean>;
setBlur(value: boolean): Promise<boolean>;
blurType: WindowsBlurType;
setVibrancy(vibrancy: MacOSVibrancy): void;
}
/**
* @deprecated
*/
export function init(): void;
/**
* @deprecated
*/
export function update(
window: Electron.BrowserWindow,
values: {
windows?: {
blurType: WindowsBlurType;
};
macos?: {
vibrancy: MacOSVibrancy;
};
linux?: {
requestBlur: boolean;
};
}
): void;
export class Hacks {
static injectOnElectron(): void;
static delayReadyEvent(): void;
}
export type WindowsBlurType =
| "acrylic"
| "blurbehind"
| "transparent"
| "none";
export type MacOSVibrancy =
| (
| "appearance-based"
| "light"
| "dark"
| "titlebar"
| "selection"
| "menu"
| "popover"
| "sidebar"
| "medium-light"
| "ultra-dark"
| "header"
| "sheet"
| "window"
| "hud"
| "fullscreen-ui"
| "tooltip"
| "content"
| "under-window"
| "under-page"
)
| null;
}
declare module "glasstron/src/utils" {
class Utils {
static getSavePath(): string;
static copyToPath(
innerFile: string,
outerFilename?: string,
flags?: number
): void;
static removeFromPath(filename: string): void;
static isInPath(filename: string): boolean;
static getPlatform(): any;
static parseKeyValString(
string: string,
keyvalSeparator?: string,
pairSeparator?: string
): any;
static makeKeyValString(
object: any,
keyvalSeparator?: string,
pairSeparator?: string
): string;
}
export = Utils;
}

View file

@ -17,15 +17,18 @@ export function addScript(scriptString: string) {
document.body.append(script); document.body.append(script);
} }
export function setup() { export function setup() {
console.log("Setting up ArmCord settings."); console.log("Setting up temporary ArmCord settings.");
storage.set( storage.set(
"settings", "settings",
{ {
customTitlebar: true, windowStyle: "default",
channel: "stable", channel: "stable",
doneSetup: true, doneSetup: true,
armcordCSP: true, armcordCSP: true,
minimizeToTray: true,
automaticPatches: false,
mods: "cumcord", mods: "cumcord",
blurType: "acrylic",
}, },
function (error) { function (error) {
if (error) throw error; if (error) throw error;
@ -35,21 +38,32 @@ export function setup() {
export async function sleep(ms: number) { export async function sleep(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms)); return new Promise((resolve) => setTimeout(resolve, ms));
} }
export async function checkIfConfigIsNew() {
if (await getConfigUnsafe("automaticPatches") == undefined) {
firstRun = true;
}
}
export function saveSettings( export function saveSettings(
customTitlebarSetting: boolean, windowStyle: string,
channelSetting: string, channelSetting: string,
armcordCSPSetting: boolean, armcordCSPSetting: boolean,
modsSetting: string minimizeToTray: boolean,
automaticPatches: boolean,
modsSetting: string,
blurType: string
) { ) {
console.log("Setting up ArmCord settings."); console.log("Setting up ArmCord settings.");
storage.set( storage.set(
"settings", "settings",
{ {
customTitlebar: customTitlebarSetting, windowStyle: windowStyle,
channel: channelSetting, channel: channelSetting,
doneSetup: true, doneSetup: true,
armcordCSP: armcordCSPSetting, armcordCSP: armcordCSPSetting,
minimizeToTray: minimizeToTray,
automaticPatches: automaticPatches,
mods: modsSetting, mods: modsSetting,
blurType: blurType
}, },
function (error) { function (error) {
if (error) throw error; if (error) throw error;
@ -74,3 +88,12 @@ export function getVersion() {
//to-do better way of doing this //to-do better way of doing this
return "3.1.0"; return "3.1.0";
} }
export async function injectJS(inject: string) {
const js = await (await fetch(`${inject}`)).text();
const el = document.createElement("script");
el.appendChild(document.createTextNode(js));
document.body.appendChild(el);
}

View file

@ -2,22 +2,49 @@
// I had to add most of the window creation code here to split both into seperete functions // I had to add most of the window creation code here to split both into seperete functions
// WHY? Because I can't use the same code for both due to annoying bug with value `frame` not responding to variables // WHY? Because I can't use the same code for both due to annoying bug with value `frame` not responding to variables
// I'm sorry for this mess but I'm not sure how to fix it. // I'm sorry for this mess but I'm not sure how to fix it.
import { BrowserWindow, shell } from "electron"; import { BrowserWindow, shell, app, ipcMain } from "electron";
import path from "path"; import path from "path";
import { contentPath } from "./main"; import { contentPath } from "./main";
import { firstRun } from "./utils"; import { checkIfConfigIsNew, firstRun, getConfigUnsafe } from "./utils";
import { registerIpc } from "./ipc"; import { registerIpc } from "./ipc";
import contextMenu from "electron-context-menu";
export let mainWindow: BrowserWindow; export let mainWindow: BrowserWindow;
import * as glasstron from "glasstron";
let guestWindows: BrowserWindow [] = [];
contextMenu({
showSaveImageAs: true,
showCopyImageAddress: true,
showSearchWithGoogle: true,
});
function doAfterDefiningTheWindow() { function doAfterDefiningTheWindow() {
checkIfConfigIsNew();
registerIpc(); registerIpc();
mainWindow.webContents.userAgent = mainWindow.webContents.userAgent =
"Mozilla/5.0 (X11; Linux x86) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36"; //fake useragent for screenshare to work "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.102 Safari/537.36"; //fake useragent for screenshare to work
mainWindow.webContents.setWindowOpenHandler(({ url }) => { mainWindow.webContents.setWindowOpenHandler(({ url }) => {
shell.openExternal(url); shell.openExternal(url);
return { action: "deny" }; return { action: "deny" };
}); });
mainWindow.webContents.session.webRequest.onBeforeRequest(
(details, callback) => {
if (/api\/v\d\/science$/g.test(details.url))
return callback({ cancel: true });
return callback({});
}
);
mainWindow.on("close", async (e) => {
if (await getConfigUnsafe("minimizeToTray")) {
e.preventDefault();
mainWindow.hide();
} else if (!await getConfigUnsafe("minimizeToTray")) {
e.preventDefault();
app.exit();
app.quit()
}
});
console.log(contentPath); console.log(contentPath);
try { try {
mainWindow.loadFile(contentPath); mainWindow.loadFile(contentPath);
@ -52,6 +79,7 @@ export function createCustomWindow() {
autoHideMenuBar: true, autoHideMenuBar: true,
webPreferences: { webPreferences: {
preload: path.join(__dirname, "preload/preload.js"), preload: path.join(__dirname, "preload/preload.js"),
spellcheck: true,
}, },
}); });
doAfterDefiningTheWindow(); doAfterDefiningTheWindow();
@ -67,7 +95,100 @@ export function createNativeWindow() {
autoHideMenuBar: true, autoHideMenuBar: true,
webPreferences: { webPreferences: {
preload: path.join(__dirname, "preload/preload.js"), preload: path.join(__dirname, "preload/preload.js"),
spellcheck: true,
}, },
}); });
doAfterDefiningTheWindow(); doAfterDefiningTheWindow();
} }
export function createGlasstronWindow() {
mainWindow = new glasstron.BrowserWindow({
width: 300,
height: 350,
title: "ArmCord",
darkTheme: true,
icon: path.join(__dirname, "/assets/icon_transparent.png"),
frame: true,
autoHideMenuBar: true,
webPreferences: {
preload: path.join(__dirname, "preload/preload.js"),
spellcheck: true,
},
});
//@ts-expect-error
mainWindow.blurType = getConfigUnsafe("blurType");
//@ts-expect-error
mainWindow.setBlur(true);
doAfterDefiningTheWindow();
}
export function createTabsHost() {
guestWindows[1] = mainWindow;
mainWindow = new BrowserWindow({
width: 300,
height: 350,
title: "ArmCord",
darkTheme: true,
icon: path.join(__dirname, "/assets/icon_transparent.png"),
frame: true,
autoHideMenuBar: true,
webPreferences: {
preload: path.join(__dirname, "preload/preload.js"),
},
});
doAfterDefiningTheWindow();
}
export function createTabsGuest(number: number) {
console.log(guestWindows)
if (guestWindows[number] !== undefined || null) {
try {
console.log("Showing Guest Window " + number);
mainWindow.hide()
guestWindows[number].show();
mainWindow = guestWindows[number];
} catch (e) {
console.error(e);
}
} else {
console.log("Creating Guest Window " + number);
mainWindow.hide();
guestWindows[number] = new BrowserWindow({
width: 800,
height: 600,
title: "ArmCord Guest Window " + number,
darkTheme: true,
icon: path.join(__dirname, "/assets/icon_transparent.png"),
frame: true,
autoHideMenuBar: true,
webPreferences: {
preload: path.join(__dirname, "preload/preload.js"),
},
});
mainWindow = guestWindows[number];
ipcMain.on("tab" + number, (event) => {
event.returnValue = true; //return true so we know the tab exists
});
guestWindows[number].webContents.userAgent =
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.102 Safari/537.36"; //fake useragent for screenshare to work
guestWindows[number].webContents.setWindowOpenHandler(({ url }) => {
shell.openExternal(url);
return { action: "deny" };
});
guestWindows[number].webContents.session.webRequest.onBeforeRequest(
(
details: { url: string },
callback: (arg0: { cancel?: boolean }) => any
) => {
if (/api\/v\d\/science$/g.test(details.url))
return callback({ cancel: true });
return callback({});
}
);
guestWindows[number].loadURL("https://discord.com/app");
}
}

View file

@ -26,7 +26,6 @@
"declaration": false, // Exports declaration files in addition, used for exporting a module. "declaration": false, // Exports declaration files in addition, used for exporting a module.
"declarationMap": false, // Allows the user to go to the source file when hitting a go-to-implementation key like F12 in VSCode for example. "declarationMap": false, // Allows the user to go to the source file when hitting a go-to-implementation key like F12 in VSCode for example.
//"declarationDir": "typings", // declarationDir allows you to separate the compiled code from the declaration files, used in conjunction with package.json's "types" property. //"declarationDir": "typings", // declarationDir allows you to separate the compiled code from the declaration files, used in conjunction with package.json's "types" property.
// Web Compatibility // // Web Compatibility //
"target": "ES2020", // ES2017 supports async/await, reducing the amount of compiled code, especially for async-heavy projects. ES2020 is from the Node 14 base (https://github.com/tsconfig/bases/blob/master/bases/node14.json) "target": "ES2020", // ES2017 supports async/await, reducing the amount of compiled code, especially for async-heavy projects. ES2020 is from the Node 14 base (https://github.com/tsconfig/bases/blob/master/bases/node14.json)
"downlevelIteration": false, // This flag adds extra support when targeting ES3, but adds extra bloat otherwise. "downlevelIteration": false, // This flag adds extra support when targeting ES3, but adds extra bloat otherwise.