Initial source code upload

This commit is contained in:
smartfridge 2021-12-24 22:56:49 +01:00
parent 3fd237446f
commit 101bc63651
41 changed files with 1117 additions and 6272 deletions

85
src/content/css/setup.css Normal file
View file

@ -0,0 +1,85 @@
/*CSS ONLY FOR INTERNAL USE (setup and loading)*/
@import url("https://kckarnige.github.io/femboi_owo/discord-font.css");
:root {
background-color: #2c2f33;
--header-secondary: #b9bbbe;
--header-primary: #fff;
--background-tertiary: #202225;
}
body {
color: white;
}
p {
color: #8e9297;
text-align: center;
font-weight: 100;
transform: translateY(-185%);
font-family: Whitney, Helvetica Neue, Helvetica, Arial, sans-serif;
text-rendering: optimizeLegibility;
font-style: italic;
}
.logo {
font-size: 0px;
text-align: center;
transform: translateY(-105%);
}
.logo:before {
content: "ARM";
color: #7289da;
font-weight: normal;
font-family: Helvetica, sans-serif;
font-size: 32px;
}
.logo:after {
content: "Cord";
color: #ffffff;
font-weight: normal;
font-family: Discordinated;
font-size: 32px;
}
span {
text-align: center;
}
.logo {
display: block;
margin-left: auto;
margin-right: auto;
max-height: 204px;
max-width: 204px;
transform: translateY(5%);
}
.container {
position: fixed;
top: 50%;
left: 50%;
color: #fff;
transform: translate(-50%, -50%);
}
button {
background-color: #7289da;
font-family: Whitney, "Helvetica Neue", Helvetica, Arial, sans-serif;
color: #ffffff;
padding: 4px;
border-radius: 5px;
left: 0;
text-align: center;
border-style: none;
outline: none;
}
button:hover {
background-color: #687dc6;
border-style: none;
outline: none;
cursor: pointer;
}

114
src/content/css/splash.css Normal file
View file

@ -0,0 +1,114 @@
/*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 {
-webkit-app-region: drag;
overflow: hidden;
margin: 0;
padding: 0;
width: 100%;
height: 100%;
background: var(--background-primary);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
* {
font-family: "Whitney", sans-serif;
box-sizing: border-box;
-webkit-user-select: none;
cursor: default;
}
video {
width: 200px;
height: 150px;
object-fit: cover;
}
#text {
font-size: 7vw;
text-align: center;
color: var(--header-primary);
font-weight: 400;
font-style: italic;
font-size: 16px;
text-transform: uppercase;
width: 100%;
}
#bar-container,
#bar-fill {
width: 180px;
height: 8px;
border-radius: 4px;
visibility: hidden;
}
#bar-container {
background-color: var(--background-secondary);
position: relative;
margin-top: 12px;
}
#bar-fill {
background-color: var(--brand-experiment);
width: 0;
}
#debug {
position: absolute;
bottom: 6px;
right: 6px;
text-align: right;
font-size: 10px;
color: var(--text-muted);
white-space: pre;
}

View file

@ -0,0 +1,123 @@
@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);
}
.titleebar {
position: absolute;
top: 0;
left: 0;
right: 0;
box-sizing: border-box;
width: 100%;
font-size: 13px;
padding: 0 16px;
overflow: hidden;
flex-shrink: 0;
align-items: center;
justify-content: center;
user-select: none;
zoom: 1;
line-height: 22px;
height: 22px;
display: flex;
z-index: 99999;
}
.titlebar {
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;
}
.appMount-3lHmkl{
}
.titlebar #window-title {
width: 30%;
height: 100%;
line-height: 30px;
float: left;
padding: 0 0 0 1em;
}
.titlebar #window-controls-container {
float: right;
width: 150px;
height: 100%;
line-height: 30px;
background-color: #202225;
-webkit-app-region: no-drag;
}
.titlebar #window-controls-container #minimize,
.titlebar #window-controls-container #maximize,
.titlebar #window-controls-container #quit {
float: left;
height: 100%;
width: 33%;
text-align: center;
color: #f7f7f7;
cursor: default;
}
.titlebar #window-controls-container #minimize:hover {
background-color: #99AAB5;
}
.titlebar #window-controls-container #maximize:hover {
background-color: #99AAB5;
}
.titlebar #window-controls-container #quit:hover {
background-color: #F04747;
}
.titlebar #window-controls-container #quit {
background-color: #f7f7f7;
-webkit-mask: url("data:image/svg+xml;charset=utf-8,%3Csvg width='11' height='11' viewBox='0 0 11 11' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M6.279 5.5L11 10.221l-.779.779L5.5 6.279.779 11 0 10.221 4.721 5.5 0 .779.779 0 5.5 4.721 10.221 0 11 .779 6.279 5.5z' fill='%23000'/%3E%3C/svg%3E") no-repeat 50% 50%;
mask: url("data:image/svg+xml;charset=utf-8,%3Csvg width='11' height='11' viewBox='0 0 11 11' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M6.279 5.5L11 10.221l-.779.779L5.5 6.279.779 11 0 10.221 4.721 5.5 0 .779.779 0 5.5 4.721 10.221 0 11 .779 6.279 5.5z' fill='%23000'/%3E%3C/svg%3E") no-repeat 50% 50%;
}
.titlebar #window-controls-container #minimize {
background-color: #f7f7f7;
-webkit-mask: url("data:image/svg+xml;charset=utf-8,%3Csvg width='11' height='11' viewBox='0 0 11 11' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M11 4.399V5.5H0V4.399h11z' fill='%23000'/%3E%3C/svg%3E") no-repeat 50% 50%;
mask: url("data:image/svg+xml;charset=utf-8,%3Csvg width='11' height='11' viewBox='0 0 11 11' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M11 4.399V5.5H0V4.399h11z' fill='%23000'/%3E%3C/svg%3E") no-repeat 50% 50%;
}
.titlebar #window-controls-container #maximize {
background-color: #f7f7f7;
-webkit-mask: url("data:image/svg+xml;charset=utf-8,%3Csvg width='11' height='11' viewBox='0 0 11 11' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M11 0v11H0V0h11zM9.899 1.101H1.1V9.9h8.8V1.1z' fill='%23000'/%3E%3C/svg%3E") no-repeat 50% 50%;
mask: url("data:image/svg+xml;charset=utf-8,%3Csvg width='11' height='11' viewBox='0 0 11 11' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M11 0v11H0V0h11zM9.899 1.101H1.1V9.9h8.8V1.1z' fill='%23000'/%3E%3C/svg%3E") no-repeat 50% 50%;
}
.window-title:after {
content: "Cord";
color: var(--cord-color) !important;
font-weight: normal;
font-size: 14px;
font-family: Discordinated;
}
.window-title:before {
content: "ARM";
color: var(--armcord-color);
font-weight: normal;
font-size: 14px;
font-family: Helvetica, sans-serif;
}
.window-title {
font-size: 0px !important;
margin-left: initial !important;
transform: translate(10px, 0px);
}

50
src/content/index.html Normal file
View file

@ -0,0 +1,50 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>ArmCord</title>
<style>
@import url("css/splash.css");
</style>
</head>
<body>
<div class="container">
<video autoplay loop class="logo">
<source
src="https://armcord.smartfridge.space/discord_loading.webm"
type="video/webm"
/>
</video>
<p id="text"></p>
</div>
</body>
<script>
const text = document.getElementById("text");
if (window.navigator.onLine === false) {
text.innerHTML =
"You appear to be offline. Please connect to the internet and try again.";
} else {
text.innerHTML = "Starting ArmCord...";
setTimeout(() => {
window.armcord.splashEnd();
switch (window.armcord.channel) {
case "stable":
window.location.href = "https://discord.com/app";
break;
case "canary":
window.location.href = "https://canary.discord.com/app";
break;
case "ptb":
window.location.href = "https://ptb.discord.com/app";
break;
case "foss":
window.location.href = "https://dev.fosscord.com/app";
break;
default:
window.location.href = "https://discord.com/app";
}}, 5000);
}
</script>
</html>

24
src/content/setup.html Normal file
View file

@ -0,0 +1,24 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<title>ArmCord Setup</title>
<style>
@import url("css/setup.css");
</style>
</head>
<body>
<div class="container">
<h1 class="logo"></h1>
<h2>Welcome to ArmCord!</h2>
<p>Select what kind of setup you want to perform:</p>
<button id="express">Express setup</button>
<button id="full">Full setup</button>
</div>
</body>
</html>

26
src/extensions/plugin.ts Normal file
View file

@ -0,0 +1,26 @@
import * as fs from 'fs';
import { app, session } from 'electron';
const userDataPath = app.getPath("userData");
const pluginFolder = userDataPath + "/plugins/";
if (!fs.existsSync(pluginFolder)) {
fs.mkdirSync(pluginFolder);
console.log("Created missing plugin folder");
}
app.whenReady().then(() => {
fs.readdirSync(pluginFolder).forEach((file) => {
try {
const manifest = fs.readFileSync(
`${userDataPath}/plugins/${file}/manifest.json`,
"utf8"
);
var pluginFile = JSON.parse(manifest);
session.defaultSession.loadExtension(`${userDataPath}/plugins/${file}`);
console.log(
`%cLoaded ${pluginFile.name} made by ${pluginFile.author}`,
"color:red"
);
} catch (err) {
console.error(err);
}
});
});

0
src/extensions/themes.ts Normal file
View file

92
src/main.ts Normal file
View file

@ -0,0 +1,92 @@
// Modules to control application life and create native browser window
import { app, BrowserWindow, ipcMain, shell } from "electron";
import * as path from "path";
import * as storage from 'electron-json-storage';
import {setup} from './utils';
import './extensions/plugin';
var isSetup = null;
var contentPath:string = "null";
var frame:boolean;
storage.keys(function(error, keys) {
if (error) throw error;
for (var key of keys) {
console.log('There is a key called: ' + key);
}
});
storage.has('firstRun', function(error, hasKey) {
if (error) throw error;
if (!hasKey) {
console.log('First run of the ArmCord. Starting setup.');
isSetup = true;
setup();
contentPath = __dirname + '/content/setup.html'
} else {
console.log('ArmCord has been run before. Skipping setup.');
isSetup = false;
contentPath = __dirname + '/content/index.html'
}
});
storage.get('settings', function(error, data:any) {
if (error) throw error;
console.log(data);
frame = data.customTitlebar;
console.log(frame)
});
function createWindow () {
const mainWindow = new BrowserWindow({
width: 300,
height: 300,
title: "ArmCord",
frame: frame,
webPreferences: {
preload: path.join(__dirname, 'preload/preload.js')
}
})
ipcMain.on("get-app-path", (event, arg) => {
event.reply("app-path", app.getAppPath());
});
ipcMain.on("open-external-link", (event, href: string) => {
shell.openExternal(href);
});
ipcMain.on("win-maximize", (event, arg) => {
mainWindow.maximize();
});
ipcMain.on("win-isMaximized", (event, arg) => {
event.returnValue = mainWindow.isMaximized();
});
ipcMain.on("win-minimize", (event, arg) => {
mainWindow.minimize();
});
ipcMain.on("win-show", (event, arg) => {
mainWindow.show();
});
ipcMain.on("win-hide", (event, arg) => {
mainWindow.hide();
});
ipcMain.on("get-app-version", (event) => {
event.returnValue = process.env.npm_package_version;
})
ipcMain.on("splashEnd", (event, arg) => {
mainWindow.setSize(800, 600);
});
ipcMain.on("channel", (event) => {
event.returnValue = storage.getSync('channel');
})
mainWindow.loadFile(contentPath)
}
app.whenReady().then(() => {
createWindow()
app.on('activate', function () {
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
})
app.on('window-all-closed', function () {
if (process.platform !== 'darwin') app.quit()
})

17
src/preload/bridge.ts Normal file
View file

@ -0,0 +1,17 @@
import { contextBridge, ipcRenderer } from 'electron';
import {getDisplayMediaSelector} from './capturer';
contextBridge.exposeInMainWorld("armcord", {
window: {
show: () => ipcRenderer.sendSync('win-show'),
hide: () => ipcRenderer.sendSync('win-hide'),
minimize: () => ipcRenderer.sendSync('win-minimize'),
maximize: () => ipcRenderer.sendSync('win-maximize'),
},
electron: process.versions.electron,
version: ipcRenderer.sendSync('get-app-version', 'app-version'),
getDisplayMediaSelector: getDisplayMediaSelector,
splashEnd: () => ipcRenderer.sendSync('splashEnd'),
channel: ipcRenderer.sendSync('channel')
});

152
src/preload/capturer.ts Normal file
View file

@ -0,0 +1,152 @@
//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
import { desktopCapturer } from 'electron';
import {addStyle, addScript} from '../utils';
const CANCEL_ID = 'desktop-capturer-selection__cancel';
export async function getDisplayMediaSelector() {
const sources = await desktopCapturer.getSources({
types: ['screen', 'window'],
});
return `<div class="desktop-capturer-selection__scroller">
<ul class="desktop-capturer-selection__list">
${sources
.map(
({ id, name, thumbnail }) => `
<li class="desktop-capturer-selection__item">
<button class="desktop-capturer-selection__btn" data-id="${id}" title="${name}">
<img class="desktop-capturer-selection__thumbnail" src="${thumbnail.toDataURL()}" />
<span class="desktop-capturer-selection__name">${name}</span>
</button>
</li>
`,
)
.join('')}
<li class="desktop-capturer-selection__item">
<button class="desktop-capturer-selection__btn" data-id="${CANCEL_ID}" title="Cancel">
<span class="desktop-capturer-selection__name desktop-capturer-selection__name--cancel">Cancel</span>
</button>
</li>
</ul>
</div>`;
}
const screenShareCSS = `
.desktop-capturer-selection {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100vh;
background: rgba(30,30,30,.75);
color: #FFFFFF;
z-index: 10000000;
display: flex;
align-items: center;
justify-content: center;
}
.desktop-capturer-selection__scroller {
width: 100%;
max-height: 100vh;
overflow-y: auto;
}
.desktop-capturer-selection__list {
max-width: calc(100% - 100px);
margin: 50px;
padding: 0;
display: flex;
flex-wrap: wrap;
list-style: none;
overflow: hidden;
justify-content: center;
}
.desktop-capturer-selection__item {
display: flex;
margin: 4px;
}
.desktop-capturer-selection__btn {
display: flex;
flex-direction: column;
align-items: stretch;
width: 145px;
margin: 0;
border: 0;
border-radius: 3px;
padding: 4px;
background: #2C2F33;
text-align: left;
@media (prefers-reduced-motion: no-preference) {
transition: background-color .15s, box-shadow .15s, color .15s;
}
color: #dedede;
}
.desktop-capturer-selection__btn:hover,
.desktop-capturer-selection__btn:focus {
background: #7289DA;
box-shadow: 0 0 4px rgba(0,0,0,0.45), 0 0 2px rgba(0,0,0,0.25);
color: #fff;
}
.desktop-capturer-selection__thumbnail {
width: 100%;
height: 81px;
object-fit: cover;
}
.desktop-capturer-selection__name {
margin: 6px 0;
white-space: nowrap;
color: white;
text-overflow: ellipsis;
text-align: center;
overflow: hidden;
}
.desktop-capturer-selection__name--cancel {
margin: auto 0;
}
`;
const screenShareJS = `
window.navigator.mediaDevices.getDisplayMedia = () => new Promise(async (resolve, reject) => {
try {
const selectionElem = document.createElement('div');
selectionElem.classList = ['desktop-capturer-selection'];
selectionElem.innerHTML = await window.electron.getDisplayMediaSelector();
document.body.appendChild(selectionElem);
document
.querySelectorAll('.desktop-capturer-selection__btn')
.forEach((button) => {
button.addEventListener('click', async () => {
try {
const id = button.getAttribute('data-id');
if (id === '${CANCEL_ID}') {
reject(new Error('Cancelled by user'));
} else {
const stream = await window.navigator.mediaDevices.getUserMedia({
audio: false,
video: {
mandatory: {
chromeMediaSource: 'desktop',
chromeMediaSourceId: id,
},
},
});
resolve(stream);
}
} catch (err) {
reject(err);
} finally {
selectionElem.remove();
}
});
});
} catch (err) {
reject(err);
}
});
`;
document.addEventListener("DOMContentLoaded", function(event) {
addScript(screenShareJS);
addStyle(screenShareCSS);
});

5
src/preload/preload.ts Normal file
View file

@ -0,0 +1,5 @@
import './capturer'
import './bridge'
import {injectTitlebar} from './titlebar';
injectTitlebar();
console.log("ArmCord");

46
src/preload/titlebar.ts Normal file
View file

@ -0,0 +1,46 @@
import { ipcRenderer } from 'electron';
import {addStyle} from '../utils'
import * as fs from 'fs';
import * as path from 'path';
export function injectTitlebar() {
document.addEventListener("DOMContentLoaded", function (event) {
var elem = document.createElement("div");
elem.innerHTML = `<nav class="titlebar">
<div class="window-title" id="window-title"></div>
<div id="window-controls-container">
<div id="minimize"></div>
<div id="maximize"></div>
<div id="quit"></div>
</div>
</nav>`;
document.body.appendChild(elem);
const cssPath = path.join(__dirname, '../', '/content/css/titlebar.css');
addStyle(fs.readFileSync(
cssPath,
"utf8"
));
var minimize = document.querySelector("#minimize");
var maximize = document.querySelector("#maximize");
var quit = document.querySelector("#quit");
minimize!.addEventListener("click", () => {
ipcRenderer.sendSync('win-minimize')
});
maximize!.addEventListener("click", () => {
if (ipcRenderer.sendSync('win-isMaximized') == true) {
ipcRenderer.sendSync('win-minimize')
} else {
ipcRenderer.sendSync('win-maximize')
}
});
quit!.addEventListener("click", () => {
ipcRenderer.sendSync('win-hide')
});
});
}
export function removeTitlebar() {
document.querySelector('#titlebar')!.remove();
}

24
src/utils.ts Normal file
View file

@ -0,0 +1,24 @@
import * as storage from 'electron-json-storage';
//utillity functions that are used all over the codebase or just too obscure to be put in the file used in
export function addStyle(styleString: string) {
const style = document.createElement('style');
style.textContent = styleString;
document.head.append(style);
};
export function addScript(scriptString: string) {
var script = document.createElement("script");
script.textContent = scriptString;
document.body.append(script);
};
export function setup(){
console.log("Setting up ArmCord settings.");
storage.set('settings', { customTitlebar: true, channel: 'stable', firstRun: 'done' }, function(error) {
if (error) throw error;
});
}
export interface settingsStructure {
channel: string,
customTitlebar: boolean,
firstRun: string,
}