a
							
								
								
									
										239
									
								
								meta/dev.vencord.Vesktop.metainfo.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,239 @@
 | 
				
			|||||||
 | 
					<?xml version="1.0" encoding="utf-8"?>
 | 
				
			||||||
 | 
					<component type="desktop-application">
 | 
				
			||||||
 | 
					  <!--Created with jdAppStreamEdit 7.1-->
 | 
				
			||||||
 | 
					  <id>dev.vencord.Vesktop</id>
 | 
				
			||||||
 | 
					  <name>Vesktop</name>
 | 
				
			||||||
 | 
					  <summary>Snappier Discord app with Vencord</summary>
 | 
				
			||||||
 | 
					  <developer_name>Vencord Contributors</developer_name>
 | 
				
			||||||
 | 
					  <launchable type="desktop-id">dev.vencord.Vesktop.desktop</launchable>
 | 
				
			||||||
 | 
					  <metadata_license>CC0-1.0</metadata_license>
 | 
				
			||||||
 | 
					  <project_license>GPL-3.0</project_license>
 | 
				
			||||||
 | 
					  <project_group>Vencord</project_group>
 | 
				
			||||||
 | 
					  <description>
 | 
				
			||||||
 | 
					    <p>Vesktop is a cross platform desktop app aiming to give you a snappier Discord experience with Vencord pre-installed.</p>
 | 
				
			||||||
 | 
					    <p>Vesktop comes bundled with Venmic, a purpose-built library to provide functioning audio screenshare.</p>
 | 
				
			||||||
 | 
					  </description>
 | 
				
			||||||
 | 
					  <screenshots>
 | 
				
			||||||
 | 
					    <screenshot type="default">
 | 
				
			||||||
 | 
					      <caption>Vencord settings page and about window open</caption>
 | 
				
			||||||
 | 
					      <image type="source">https://vencord.dev/assets/screenshots/vesktop-1-appstream.png</image>
 | 
				
			||||||
 | 
					    </screenshot>
 | 
				
			||||||
 | 
					    <screenshot>
 | 
				
			||||||
 | 
					      <caption>A dialog showing screenshare options</caption>
 | 
				
			||||||
 | 
					      <image type="source">https://vencord.dev/assets/screenshots/vesktop-2-appstream.png</image>
 | 
				
			||||||
 | 
					    </screenshot>
 | 
				
			||||||
 | 
					    <screenshot>
 | 
				
			||||||
 | 
					      <caption>A screenshot of a Discord server</caption>
 | 
				
			||||||
 | 
					      <image type="source">https://vencord.dev/assets/screenshots/vesktop-3-appstream.png</image>
 | 
				
			||||||
 | 
					    </screenshot>
 | 
				
			||||||
 | 
					  </screenshots>
 | 
				
			||||||
 | 
					  <releases>
 | 
				
			||||||
 | 
					    <release version="1.5.3" date="2024-07-04" type="stable">
 | 
				
			||||||
 | 
					      <url>https://github.com/Vencord/Vesktop/releases/tag/v1.5.3</url>
 | 
				
			||||||
 | 
					      <description>
 | 
				
			||||||
 | 
					        <p>Features</p>
 | 
				
			||||||
 | 
					        <ul>
 | 
				
			||||||
 | 
					          <li>added arm64 Windows support</li>
 | 
				
			||||||
 | 
					          <li>windows & macOS builds are now universal</li>
 | 
				
			||||||
 | 
					          <li>added option to configure spellcheck languages</li>
 | 
				
			||||||
 | 
					          <li>will auto-update from now on</li>
 | 
				
			||||||
 | 
					          <li>updated electron to 31 & Chromium to 126</li>
 | 
				
			||||||
 | 
					          <li>macOS: Added customized dmg background by @khcrysalis</li>
 | 
				
			||||||
 | 
					          <li>Windows Portable: store settings in portable folder by @MrGarlic1</li>
 | 
				
			||||||
 | 
					          <li>linux audioshare: added granular selection, more options, better ui by @Curve</li>
 | 
				
			||||||
 | 
					          <li>changed default screen-sharing quality to 720p 30 FPS by @Tiagoquix</li>
 | 
				
			||||||
 | 
					        </ul>
 | 
				
			||||||
 | 
					        <p>Fixes</p>
 | 
				
			||||||
 | 
					        <ul>
 | 
				
			||||||
 | 
					          <li>macOS: Added workaround for making things in draggable area clickable by @HAHALOSAH</li>
 | 
				
			||||||
 | 
					          <li>fixed Screenshare UI for non-linux systems by @PolisanTheEasyNick</li>
 | 
				
			||||||
 | 
					          <li>fixed opening on screen that was disconnected by @MrGarlic1</li>
 | 
				
			||||||
 | 
					          <li>mac: hide native window controls with custom titlebar enabled by @MrGarlic1</li>
 | 
				
			||||||
 | 
					          <li>fixed some broken patches by @D3SOX</li>
 | 
				
			||||||
 | 
					          <li>fixed framerate in constraints by @kittykel</li>
 | 
				
			||||||
 | 
					          <li>fixed some first launch switches not applying</li>
 | 
				
			||||||
 | 
					          <li>fixed potential sandbox escape via custom vencord location</li>
 | 
				
			||||||
 | 
					        </ul>
 | 
				
			||||||
 | 
					      </description>
 | 
				
			||||||
 | 
					    </release>
 | 
				
			||||||
 | 
					    <release version="1.5.2" date="2024-05-01" type="stable">
 | 
				
			||||||
 | 
					      <url>https://github.com/Vencord/Vesktop/releases/tag/v1.5.2</url>
 | 
				
			||||||
 | 
					      <description>
 | 
				
			||||||
 | 
					        <p>What's Changed</p>
 | 
				
			||||||
 | 
					        <ul>
 | 
				
			||||||
 | 
					          <li>Fixed scrollbars looking wrong (actually Discord's fault)</li>
 | 
				
			||||||
 | 
					          <li>Tray: Added left click hide/show feature by @0bCdian</li>
 | 
				
			||||||
 | 
					          <li>MacOS: Fixed the app not properly requesting microphone permissions by @ssalggnikool</li>
 | 
				
			||||||
 | 
					          <li>Linux: Various fixed related to audio screenshare by @Curve</li>
 | 
				
			||||||
 | 
					          <li>Linux: Overhauled & improved screenshare with better framerate by @kaitlynkittyy</li>
 | 
				
			||||||
 | 
					          <li>Users can now pass --enable/disable-features command line flags by @takase1121</li>
 | 
				
			||||||
 | 
					        </ul>
 | 
				
			||||||
 | 
					      </description>
 | 
				
			||||||
 | 
					    </release>
 | 
				
			||||||
 | 
					    <release version="1.5.1" date="2024-03-12" type="stable">
 | 
				
			||||||
 | 
					      <url>https://github.com/Vencord/Vesktop/releases/tag/v1.5.1</url>
 | 
				
			||||||
 | 
					      <description>
 | 
				
			||||||
 | 
					        <p>New Features</p>
 | 
				
			||||||
 | 
					        <ul>
 | 
				
			||||||
 | 
					          <li>Added categories to Vesktop settings to reduce visual clutter by @justin13888</li>
 | 
				
			||||||
 | 
					          <li>Added support for Vencord's transparent window options</li>
 | 
				
			||||||
 | 
					        </ul>
 | 
				
			||||||
 | 
					        <p>Fixes</p>
 | 
				
			||||||
 | 
					        <ul>
 | 
				
			||||||
 | 
					          <li>Fixed ugly error popups when starting Vesktop without working internet connection</li>
 | 
				
			||||||
 | 
					          <li>Fixed popout title bars on Windows</li>
 | 
				
			||||||
 | 
					          <li>Fixed spellcheck entries</li>
 | 
				
			||||||
 | 
					          <li>Fixed screenshare audio using microphone on debian, by @Curve</li>
 | 
				
			||||||
 | 
					          <li>Fixed a bug where autostart on Linux won't preserve command line flags</li>
 | 
				
			||||||
 | 
					        </ul>
 | 
				
			||||||
 | 
					      </description>
 | 
				
			||||||
 | 
					    </release>
 | 
				
			||||||
 | 
					    <release version="1.5.0" date="2024-01-16" type="stable">
 | 
				
			||||||
 | 
					      <url>https://github.com/Vencord/Vesktop/releases/tag/v1.5.0</url>
 | 
				
			||||||
 | 
					      <description>
 | 
				
			||||||
 | 
					        <p>What's Changed</p>
 | 
				
			||||||
 | 
					        <ul>
 | 
				
			||||||
 | 
					          <li>fully renamed to Vesktop. You will likely have to login to Discord again. You might have to re-create your vesktop shortcut</li>
 | 
				
			||||||
 | 
					          <li>added option to disable smooth scrolling by @ZirixCZ</li>
 | 
				
			||||||
 | 
					          <li>added setting to disable hardware acceleration by @zt64</li>
 | 
				
			||||||
 | 
					          <li>fixed adding connections</li>
 | 
				
			||||||
 | 
					          <li>fixed / improved discord popouts</li>
 | 
				
			||||||
 | 
					          <li>you can now use the custom discord titlebar on linux/mac</li>
 | 
				
			||||||
 | 
					          <li>the splash window is now draggable</li>
 | 
				
			||||||
 | 
					          <li>now signed on mac</li>
 | 
				
			||||||
 | 
					        </ul>
 | 
				
			||||||
 | 
					      </description>
 | 
				
			||||||
 | 
					    </release>
 | 
				
			||||||
 | 
					    <release version="0.4.4" date="2023-12-02" type="stable">
 | 
				
			||||||
 | 
					      <url>https://github.com/Vencord/Vesktop/releases/tag/v0.4.4</url>
 | 
				
			||||||
 | 
					      <description>
 | 
				
			||||||
 | 
					        <p>What's Changed</p>
 | 
				
			||||||
 | 
					        <ul>
 | 
				
			||||||
 | 
					          <li>improve venmic system compatibility by @Curve</li>
 | 
				
			||||||
 | 
					          <li>Update steamdeck controller layout by @AAGaming00</li>
 | 
				
			||||||
 | 
					          <li>feat: Add option to disable smooth scrolling by @ZirixCZ</li>
 | 
				
			||||||
 | 
					          <li>unblur shiggy in splash screen by @viacoro</li>
 | 
				
			||||||
 | 
					          <li>update electron & arrpc @D3SOX</li>
 | 
				
			||||||
 | 
					        </ul>
 | 
				
			||||||
 | 
					      </description>
 | 
				
			||||||
 | 
					    </release>
 | 
				
			||||||
 | 
					    <release version="0.4.3" date="2023-11-01" type="stable">
 | 
				
			||||||
 | 
					      <url>https://github.com/Vencord/Vesktop/releases/tag/v0.4.3</url>
 | 
				
			||||||
 | 
					    </release>
 | 
				
			||||||
 | 
					    <release version="0.4.2" date="2023-10-26" type="stable">
 | 
				
			||||||
 | 
					      <url>https://github.com/Vencord/Vesktop/releases/tag/v0.4.2</url>
 | 
				
			||||||
 | 
					    </release>
 | 
				
			||||||
 | 
					    <release version="0.4.1" date="2023-10-24" type="stable">
 | 
				
			||||||
 | 
					      <url>https://github.com/Vencord/Vesktop/releases/tag/v0.4.1</url>
 | 
				
			||||||
 | 
					    </release>
 | 
				
			||||||
 | 
					    <release version="0.4.0" date="2023-10-21" type="stable">
 | 
				
			||||||
 | 
					      <url>https://github.com/Vencord/Vesktop/releases/tag/v0.4.0</url>
 | 
				
			||||||
 | 
					    </release>
 | 
				
			||||||
 | 
					    <release version="0.3.3" date="2023-09-30" type="stable">
 | 
				
			||||||
 | 
					      <url>https://github.com/Vencord/Vesktop/releases/tag/v0.3.3</url>
 | 
				
			||||||
 | 
					    </release>
 | 
				
			||||||
 | 
					    <release version="0.3.2" date="2023-09-25" type="stable">
 | 
				
			||||||
 | 
					      <url>https://github.com/Vencord/Vesktop/releases/tag/v0.3.2</url>
 | 
				
			||||||
 | 
					    </release>
 | 
				
			||||||
 | 
					    <release version="0.3.1" date="2023-09-25" type="stable">
 | 
				
			||||||
 | 
					      <url>https://github.com/Vencord/Vesktop/releases/tag/v0.3.1</url>
 | 
				
			||||||
 | 
					    </release>
 | 
				
			||||||
 | 
					    <release version="0.3.0" date="2023-08-16" type="stable">
 | 
				
			||||||
 | 
					      <url>https://github.com/Vencord/Vesktop/releases/tag/v0.3.0</url>
 | 
				
			||||||
 | 
					    </release>
 | 
				
			||||||
 | 
					    <release version="0.2.9" date="2023-08-12" type="stable">
 | 
				
			||||||
 | 
					      <url>https://github.com/Vencord/Vesktop/releases/tag/v0.2.9</url>
 | 
				
			||||||
 | 
					    </release>
 | 
				
			||||||
 | 
					    <release version="0.2.8" date="2023-08-02" type="stable">
 | 
				
			||||||
 | 
					      <url>https://github.com/Vencord/Vesktop/releases/tag/v0.2.8</url>
 | 
				
			||||||
 | 
					    </release>
 | 
				
			||||||
 | 
					    <release version="0.2.7" date="2023-07-26" type="stable">
 | 
				
			||||||
 | 
					      <url>https://github.com/Vencord/Vesktop/releases/tag/v0.2.7</url>
 | 
				
			||||||
 | 
					    </release>
 | 
				
			||||||
 | 
					    <release version="0.2.6" date="2023-07-04" type="stable">
 | 
				
			||||||
 | 
					      <url>https://github.com/Vencord/Vesktop/releases/tag/v0.2.6</url>
 | 
				
			||||||
 | 
					    </release>
 | 
				
			||||||
 | 
					    <release version="0.2.5" date="2023-06-26" type="stable">
 | 
				
			||||||
 | 
					      <url>https://github.com/Vencord/Vesktop/releases/tag/v0.2.5</url>
 | 
				
			||||||
 | 
					    </release>
 | 
				
			||||||
 | 
					    <release version="0.2.4" date="2023-06-25" type="stable">
 | 
				
			||||||
 | 
					      <url>https://github.com/Vencord/Vesktop/releases/tag/v0.2.4</url>
 | 
				
			||||||
 | 
					    </release>
 | 
				
			||||||
 | 
					    <release version="0.2.3" date="2023-06-23" type="stable">
 | 
				
			||||||
 | 
					      <url>https://github.com/Vencord/Vesktop/releases/tag/v0.2.3</url>
 | 
				
			||||||
 | 
					    </release>
 | 
				
			||||||
 | 
					    <release version="0.2.2" date="2023-06-21" type="stable">
 | 
				
			||||||
 | 
					      <url>https://github.com/Vencord/Vesktop/releases/tag/v0.2.2</url>
 | 
				
			||||||
 | 
					    </release>
 | 
				
			||||||
 | 
					    <release version="0.2.1" date="2023-06-21" type="stable">
 | 
				
			||||||
 | 
					      <url>https://github.com/Vencord/Vesktop/releases/tag/v0.2.1</url>
 | 
				
			||||||
 | 
					    </release>
 | 
				
			||||||
 | 
					    <release version="0.2.0" date="2023-05-03" type="stable">
 | 
				
			||||||
 | 
					      <url>https://github.com/Vencord/Vesktop/releases/tag/v0.2.0</url>
 | 
				
			||||||
 | 
					    </release>
 | 
				
			||||||
 | 
					    <release version="0.1.9" date="2023-04-27" type="stable">
 | 
				
			||||||
 | 
					      <url>https://github.com/Vencord/Vesktop/releases/tag/v0.1.9</url>
 | 
				
			||||||
 | 
					    </release>
 | 
				
			||||||
 | 
					    <release version="0.1.8" date="2023-04-15" type="stable">
 | 
				
			||||||
 | 
					      <url>https://github.com/Vencord/Vesktop/releases/tag/v0.1.8</url>
 | 
				
			||||||
 | 
					    </release>
 | 
				
			||||||
 | 
					    <release version="0.1.7" date="2023-04-15" type="stable">
 | 
				
			||||||
 | 
					      <url>https://github.com/Vencord/Vesktop/releases/tag/v0.1.7</url>
 | 
				
			||||||
 | 
					    </release>
 | 
				
			||||||
 | 
					    <release version="0.1.6" date="2023-04-11" type="stable">
 | 
				
			||||||
 | 
					      <url>https://github.com/Vencord/Vesktop/releases/tag/v0.1.6</url>
 | 
				
			||||||
 | 
					    </release>
 | 
				
			||||||
 | 
					    <release version="0.1.5" date="2023-04-10" type="stable">
 | 
				
			||||||
 | 
					      <url>https://github.com/Vencord/Vesktop/releases/tag/v0.1.5</url>
 | 
				
			||||||
 | 
					    </release>
 | 
				
			||||||
 | 
					    <release version="0.1.4" date="2023-04-09" type="stable">
 | 
				
			||||||
 | 
					      <url>https://github.com/Vencord/Vesktop/releases/tag/v0.1.4</url>
 | 
				
			||||||
 | 
					    </release>
 | 
				
			||||||
 | 
					    <release version="0.1.3" date="2023-04-06" type="stable">
 | 
				
			||||||
 | 
					      <url>https://github.com/Vencord/Vesktop/releases/tag/v0.1.3</url>
 | 
				
			||||||
 | 
					    </release>
 | 
				
			||||||
 | 
					    <release version="0.1.2" date="2023-04-05" type="stable">
 | 
				
			||||||
 | 
					      <url>https://github.com/Vencord/Vesktop/releases/tag/v0.1.2</url>
 | 
				
			||||||
 | 
					    </release>
 | 
				
			||||||
 | 
					    <release version="0.1.1" date="2023-04-04" type="stable">
 | 
				
			||||||
 | 
					      <url>https://github.com/Vencord/Vesktop/releases/tag/v0.1.1</url>
 | 
				
			||||||
 | 
					    </release>
 | 
				
			||||||
 | 
					    <release version="0.1.0" date="2023-04-04" type="development">
 | 
				
			||||||
 | 
					      <url>https://github.com/Vencord/Vesktop/releases/tag/v0.1.0</url>
 | 
				
			||||||
 | 
					    </release>
 | 
				
			||||||
 | 
					  </releases>
 | 
				
			||||||
 | 
					  <url type="homepage">https://vencord.dev/</url>
 | 
				
			||||||
 | 
					  <url type="bugtracker">https://github.com/Vencord/Vesktop/issues</url>
 | 
				
			||||||
 | 
					  <url type="faq">https://vencord.dev/faq/</url>
 | 
				
			||||||
 | 
					  <url type="help">https://github.com/Vencord/Vesktop/issues</url>
 | 
				
			||||||
 | 
					  <url type="donation">https://github.com/sponsors/Vendicated</url>
 | 
				
			||||||
 | 
					  <url type="vcs-browser">https://github.com/Vencord/Vesktop</url>
 | 
				
			||||||
 | 
					  <categories>
 | 
				
			||||||
 | 
					    <category>InstantMessaging</category>
 | 
				
			||||||
 | 
					    <category>Network</category>
 | 
				
			||||||
 | 
					  </categories>
 | 
				
			||||||
 | 
					  <requires>
 | 
				
			||||||
 | 
					    <control>pointing</control>
 | 
				
			||||||
 | 
					    <control>keyboard</control>
 | 
				
			||||||
 | 
					    <display_length compare="ge">420</display_length>
 | 
				
			||||||
 | 
					    <internet>always</internet>
 | 
				
			||||||
 | 
					  </requires>
 | 
				
			||||||
 | 
					  <recommends>
 | 
				
			||||||
 | 
					    <control>voice</control>
 | 
				
			||||||
 | 
					    <display_length compare="ge">760</display_length>
 | 
				
			||||||
 | 
					    <display_length compare="le">1200</display_length>
 | 
				
			||||||
 | 
					  </recommends>
 | 
				
			||||||
 | 
					  <content_rating type="oars-1.1">
 | 
				
			||||||
 | 
					    <content_attribute id="social-chat">intense</content_attribute>
 | 
				
			||||||
 | 
					    <content_attribute id="social-audio">intense</content_attribute>
 | 
				
			||||||
 | 
					    <content_attribute id="social-contacts">intense</content_attribute>
 | 
				
			||||||
 | 
					    <content_attribute id="social-info">intense</content_attribute>
 | 
				
			||||||
 | 
					  </content_rating>
 | 
				
			||||||
 | 
					  <keywords>
 | 
				
			||||||
 | 
					    <keyword>Discord</keyword>
 | 
				
			||||||
 | 
					    <keyword>Vencord</keyword>
 | 
				
			||||||
 | 
					    <keyword>Vesktop</keyword>
 | 
				
			||||||
 | 
					    <keyword>Privacy</keyword>
 | 
				
			||||||
 | 
					    <keyword>Mod</keyword>
 | 
				
			||||||
 | 
					  </keywords>
 | 
				
			||||||
 | 
					</component>
 | 
				
			||||||
							
								
								
									
										14
									
								
								patches/arrpc@3.5.0.patch
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,14 @@
 | 
				
			|||||||
 | 
					diff --git a/src/process/index.js b/src/process/index.js
 | 
				
			||||||
 | 
					index 97ea6514b54dd9c5df588c78f0397d31ab5f882a..c2bdbd6aaa5611bc6ff1d993beeb380b1f5ec575 100644
 | 
				
			||||||
 | 
					--- a/src/process/index.js
 | 
				
			||||||
 | 
					+++ b/src/process/index.js
 | 
				
			||||||
 | 
					@@ -5,8 +5,7 @@ import fs from 'node:fs';
 | 
				
			||||||
 | 
					 import { dirname, join } from 'path';
 | 
				
			||||||
 | 
					 import { fileURLToPath } from 'url';
 | 
				
			||||||
 | 
					 
 | 
				
			||||||
 | 
					-const __dirname = dirname(fileURLToPath(import.meta.url));
 | 
				
			||||||
 | 
					-const DetectableDB = JSON.parse(fs.readFileSync(join(__dirname, 'detectable.json'), 'utf8'));
 | 
				
			||||||
 | 
					+const DetectableDB = require('./detectable.json');
 | 
				
			||||||
 | 
					 
 | 
				
			||||||
 | 
					 import * as Natives from './native/index.js';
 | 
				
			||||||
 | 
					 const Native = Natives[process.platform];
 | 
				
			||||||
							
								
								
									
										69
									
								
								scripts/build/build.mts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,69 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * SPDX-License-Identifier: GPL-3.0
 | 
				
			||||||
 | 
					 * Modified for Aerocord, originally part of Vesktop, a desktop app aiming to give you a snappier Discord Experience
 | 
				
			||||||
 | 
					 * Copyright (c) 2024-2024 Aiek 
 | 
				
			||||||
 | 
					 * Copyright (c) 2024 RandomServer Community
 | 
				
			||||||
 | 
					 * Copyright (c) 2023 Vendicated and Vencord contributors
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { BuildOptions, build } from "esbuild";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const isDev = process.argv.includes("--dev");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const CommonOpts: BuildOptions = {
 | 
				
			||||||
 | 
					    minify: true,
 | 
				
			||||||
 | 
					    bundle: true,
 | 
				
			||||||
 | 
					    sourcemap: "inline",
 | 
				
			||||||
 | 
					    logLevel: "info"
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const NodeCommonOpts: BuildOptions = {
 | 
				
			||||||
 | 
					    ...CommonOpts,
 | 
				
			||||||
 | 
					    format: "cjs",
 | 
				
			||||||
 | 
					    platform: "node",
 | 
				
			||||||
 | 
					    external: ["electron"],
 | 
				
			||||||
 | 
					    target: ["esnext"],
 | 
				
			||||||
 | 
					    define: {
 | 
				
			||||||
 | 
					        IS_DEV: JSON.stringify(isDev)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async function buildUnpacked() {
 | 
				
			||||||
 | 
					    await Promise.all([
 | 
				
			||||||
 | 
					        build({
 | 
				
			||||||
 | 
					            ...NodeCommonOpts,
 | 
				
			||||||
 | 
					            entryPoints: ["src/main/index.ts"],
 | 
				
			||||||
 | 
					            outfile: "dist/js/main.js",
 | 
				
			||||||
 | 
					            footer: { js: "//# sourceURL=VCDMain" }
 | 
				
			||||||
 | 
					        }),
 | 
				
			||||||
 | 
					        build({
 | 
				
			||||||
 | 
					            ...NodeCommonOpts,
 | 
				
			||||||
 | 
					            entryPoints: ["src/preload/index.ts"],
 | 
				
			||||||
 | 
					            outfile: "dist/js/preload.js",
 | 
				
			||||||
 | 
					            footer: { js: "//# sourceURL=VCDPreload" }
 | 
				
			||||||
 | 
					        }),
 | 
				
			||||||
 | 
					        build({
 | 
				
			||||||
 | 
					            ...NodeCommonOpts,
 | 
				
			||||||
 | 
					            entryPoints: ["src/updater/preload.ts"],
 | 
				
			||||||
 | 
					            outfile: "dist/js/updaterPreload.js",
 | 
				
			||||||
 | 
					            footer: { js: "//# sourceURL=VCDUpdaterPreload" }
 | 
				
			||||||
 | 
					        }),
 | 
				
			||||||
 | 
					        build({
 | 
				
			||||||
 | 
					            ...CommonOpts,
 | 
				
			||||||
 | 
					            globalName: "Vesktop",
 | 
				
			||||||
 | 
					            entryPoints: ["src/renderer/index.ts"],
 | 
				
			||||||
 | 
					            outfile: "dist/js/renderer.js",
 | 
				
			||||||
 | 
					            format: "iife",
 | 
				
			||||||
 | 
					            inject: ["./scripts/build/injectReact.mjs"],
 | 
				
			||||||
 | 
					            jsxFactory: "VencordCreateElement",
 | 
				
			||||||
 | 
					            jsxFragment: "VencordFragment",
 | 
				
			||||||
 | 
					            external: ["@vencord/types/*"],
 | 
				
			||||||
 | 
					            footer: { js: "//# sourceURL=VCDRenderer" }
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					    ]);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					buildUnpacked().catch(err => {
 | 
				
			||||||
 | 
					    console.error("Build failed:", err);
 | 
				
			||||||
 | 
					    process.exit(1);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										11
									
								
								scripts/build/injectReact.mjs
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,11 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * SPDX-License-Identifier: GPL-3.0
 | 
				
			||||||
 | 
					 * Modified for Aerocord, originally part of Vesktop, a desktop app aiming to give you a snappier Discord Experience
 | 
				
			||||||
 | 
					 * Copyright (c) 2024-2024 Aiek 
 | 
				
			||||||
 | 
					 * Copyright (c) 2024 RandomServer Community
 | 
				
			||||||
 | 
					 * Copyright (c) 2023 Vendicated and Vencord contributors
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const VencordFragment = /* #__PURE__*/ Symbol.for("react.fragment");
 | 
				
			||||||
 | 
					export let VencordCreateElement = (...args) =>
 | 
				
			||||||
 | 
					    (VencordCreateElement = Vencord.Webpack.Common.React.createElement)(...args);
 | 
				
			||||||
							
								
								
									
										76
									
								
								scripts/build/sandboxFix.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,76 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * SPDX-License-Identifier: GPL-3.0
 | 
				
			||||||
 | 
					 * Modified for Aerocord, originally part of Vesktop, a desktop app aiming to give you a snappier Discord Experience
 | 
				
			||||||
 | 
					 * Copyright (c) 2024-2024 Aiek 
 | 
				
			||||||
 | 
					 * Copyright (c) 2024 RandomServer Community
 | 
				
			||||||
 | 
					 * Copyright (c) 2023 Vendicated and Vencord contributors
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Based on https://github.com/gergof/electron-builder-sandbox-fix/blob/master/lib/index.js
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const fs = require("fs/promises");
 | 
				
			||||||
 | 
					const path = require("path");
 | 
				
			||||||
 | 
					let isApplied = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const hook = async () => {
 | 
				
			||||||
 | 
					    if (isApplied) return;
 | 
				
			||||||
 | 
					    isApplied = true;
 | 
				
			||||||
 | 
					    if (process.platform !== "linux") {
 | 
				
			||||||
 | 
					        // this fix is only required on linux
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    const AppImageTarget = require("app-builder-lib/out/targets/AppImageTarget");
 | 
				
			||||||
 | 
					    const oldBuildMethod = AppImageTarget.default.prototype.build;
 | 
				
			||||||
 | 
					    AppImageTarget.default.prototype.build = async function (...args) {
 | 
				
			||||||
 | 
					        console.log("Running AppImage builder hook", args);
 | 
				
			||||||
 | 
					        const oldPath = args[0];
 | 
				
			||||||
 | 
					        const newPath = oldPath + "-appimage-sandbox-fix";
 | 
				
			||||||
 | 
					        // just in case
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            await fs.rm(newPath, {
 | 
				
			||||||
 | 
					                recursive: true
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        } catch {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        console.log("Copying to apply appimage fix", oldPath, newPath);
 | 
				
			||||||
 | 
					        await fs.cp(oldPath, newPath, {
 | 
				
			||||||
 | 
					            recursive: true
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        args[0] = newPath;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const executable = path.join(newPath, this.packager.executableName);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const loaderScript = `
 | 
				
			||||||
 | 
					#!/usr/bin/env bash
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					SCRIPT_DIR="$( cd "$( dirname "\${BASH_SOURCE[0]}" )" && pwd )"
 | 
				
			||||||
 | 
					IS_STEAMOS=0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if [[ "$SteamOS" == "1" && "$SteamGamepadUI" == "1" ]]; then
 | 
				
			||||||
 | 
					    echo "Running Vesktop on SteamOS, disabling sandbox"
 | 
				
			||||||
 | 
					    IS_STEAMOS=1
 | 
				
			||||||
 | 
					fi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					exec "$SCRIPT_DIR/${this.packager.executableName}.bin" "$([ "$IS_STEAMOS" == 1 ] && echo '--no-sandbox')" "$@"
 | 
				
			||||||
 | 
					                `.trim();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            await fs.rename(executable, executable + ".bin");
 | 
				
			||||||
 | 
					            await fs.writeFile(executable, loaderScript);
 | 
				
			||||||
 | 
					            await fs.chmod(executable, 0o755);
 | 
				
			||||||
 | 
					        } catch (e) {
 | 
				
			||||||
 | 
					            console.error("failed to create loder for sandbox fix: " + e.message);
 | 
				
			||||||
 | 
					            throw new Error("Failed to create loader for sandbox fix");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const ret = await oldBuildMethod.apply(this, args);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        await fs.rm(newPath, {
 | 
				
			||||||
 | 
					            recursive: true
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return ret;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					module.exports = hook;
 | 
				
			||||||
							
								
								
									
										40
									
								
								scripts/build/vencordDep.mts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,40 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * SPDX-License-Identifier: GPL-3.0
 | 
				
			||||||
 | 
					 * Modified for Aerocord, originally part of Vesktop, a desktop app aiming to give you a snappier Discord Experience
 | 
				
			||||||
 | 
					 * Copyright (c) 2024-2024 Aiek 
 | 
				
			||||||
 | 
					 * Copyright (c) 2024 RandomServer Community
 | 
				
			||||||
 | 
					 * Copyright (c) 2023 Vendicated and Vencord contributors
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { globalExternalsWithRegExp } from "@fal-works/esbuild-plugin-global-externals";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const names = {
 | 
				
			||||||
 | 
					    webpack: "Vencord.Webpack",
 | 
				
			||||||
 | 
					    "webpack/common": "Vencord.Webpack.Common",
 | 
				
			||||||
 | 
					    utils: "Vencord.Util",
 | 
				
			||||||
 | 
					    api: "Vencord.Api",
 | 
				
			||||||
 | 
					    "api/settings": "Vencord",
 | 
				
			||||||
 | 
					    components: "Vencord.Components"
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default globalExternalsWithRegExp({
 | 
				
			||||||
 | 
					    getModuleInfo(modulePath) {
 | 
				
			||||||
 | 
					        const path = modulePath.replace("@vencord/types/", "");
 | 
				
			||||||
 | 
					        let varName = names[path];
 | 
				
			||||||
 | 
					        if (!varName) {
 | 
				
			||||||
 | 
					            const altMapping = names[path.split("/")[0]];
 | 
				
			||||||
 | 
					            if (!altMapping) throw new Error("Unknown module path: " + modulePath);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            varName =
 | 
				
			||||||
 | 
					                altMapping +
 | 
				
			||||||
 | 
					                "." +
 | 
				
			||||||
 | 
					                // @ts-ignore
 | 
				
			||||||
 | 
					                path.split("/")[1].replaceAll("/", ".");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return {
 | 
				
			||||||
 | 
					            varName,
 | 
				
			||||||
 | 
					            type: "cjs"
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    modulePathFilter: /^@vencord\/types.+$/
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										7
									
								
								scripts/header.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,7 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * SPDX-License-Identifier: GPL-3.0
 | 
				
			||||||
 | 
					 * Modified for Aerocord, originally part of Vesktop, a desktop app aiming to give you a snappier Discord Experience
 | 
				
			||||||
 | 
					 * Copyright (c) 2024-2024 Aiek 
 | 
				
			||||||
 | 
					 * Copyright (c) 2024 RandomServer Community
 | 
				
			||||||
 | 
					 * Copyright (c) 2023 Vendicated and Vencord contributors
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
							
								
								
									
										13
									
								
								scripts/start.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,13 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * SPDX-License-Identifier: GPL-3.0
 | 
				
			||||||
 | 
					 * Modified for Aerocord, originally part of Vesktop, a desktop app aiming to give you a snappier Discord Experience
 | 
				
			||||||
 | 
					 * Copyright (c) 2024-2024 Aiek 
 | 
				
			||||||
 | 
					 * Copyright (c) 2024 RandomServer Community
 | 
				
			||||||
 | 
					 * Copyright (c) 2023 Vendicated and Vencord contributors
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import "./utils/dotenv";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { spawnNodeModuleBin } from "./utils/spawn.mjs";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					spawnNodeModuleBin("electron", [process.cwd(), ...(process.env.ELECTRON_LAUNCH_FLAGS?.split(" ") ?? [])]);
 | 
				
			||||||
							
								
								
									
										12
									
								
								scripts/startWatch.mts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,12 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * SPDX-License-Identifier: GPL-3.0
 | 
				
			||||||
 | 
					 * Modified for Aerocord, originally part of Vesktop, a desktop app aiming to give you a snappier Discord Experience
 | 
				
			||||||
 | 
					 * Copyright (c) 2024-2024 Aiek 
 | 
				
			||||||
 | 
					 * Copyright (c) 2024 RandomServer Community
 | 
				
			||||||
 | 
					 * Copyright (c) 2023 Vendicated and Vencord contributors
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import "./start";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { spawnNodeModuleBin } from "./utils/spawn.mjs";
 | 
				
			||||||
 | 
					spawnNodeModuleBin("tsx", ["scripts/build/build.mts", "--", "--watch", "--dev"]);
 | 
				
			||||||
							
								
								
									
										11
									
								
								scripts/utils/dotenv.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,11 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * SPDX-License-Identifier: GPL-3.0
 | 
				
			||||||
 | 
					 * Modified for Aerocord, originally part of Vesktop, a desktop app aiming to give you a snappier Discord Experience
 | 
				
			||||||
 | 
					 * Copyright (c) 2024-2024 Aiek 
 | 
				
			||||||
 | 
					 * Copyright (c) 2024 RandomServer Community
 | 
				
			||||||
 | 
					 * Copyright (c) 2023 Vendicated and Vencord contributors
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { config } from "dotenv";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					config();
 | 
				
			||||||
							
								
								
									
										20
									
								
								scripts/utils/spawn.mts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,20 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * SPDX-License-Identifier: GPL-3.0
 | 
				
			||||||
 | 
					 * Modified for Aerocord, originally part of Vesktop, a desktop app aiming to give you a snappier Discord Experience
 | 
				
			||||||
 | 
					 * Copyright (c) 2024-2024 Aiek 
 | 
				
			||||||
 | 
					 * Copyright (c) 2024 RandomServer Community
 | 
				
			||||||
 | 
					 * Copyright (c) 2023 Vendicated and Vencord contributors
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { spawn as spaaawn, SpawnOptions } from "child_process";
 | 
				
			||||||
 | 
					import { join } from "path";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const EXT = process.platform === "win32" ? ".cmd" : "";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const OPTS: SpawnOptions = {
 | 
				
			||||||
 | 
					    stdio: "inherit"
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function spawnNodeModuleBin(bin: string, args: string[]) {
 | 
				
			||||||
 | 
					    spaaawn(join("node_modules", ".bin", bin + EXT), args, OPTS);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										95
									
								
								scripts/utils/updateMeta.mts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,95 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * SPDX-License-Identifier: GPL-3.0
 | 
				
			||||||
 | 
					 * Modified for Aerocord, originally part of Vesktop, a desktop app aiming to give you a snappier Discord Experience
 | 
				
			||||||
 | 
					 * Copyright (c) 2024-2024 Aiek 
 | 
				
			||||||
 | 
					 * Copyright (c) 2024 RandomServer Community
 | 
				
			||||||
 | 
					 * Copyright (c) 2023 Vendicated and Vencord contributors
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { promises as fs } from "node:fs";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { DOMParser, XMLSerializer } from "@xmldom/xmldom";
 | 
				
			||||||
 | 
					import xmlFormat from "xml-formatter";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function generateDescription(description: string, descriptionNode: Element) {
 | 
				
			||||||
 | 
					    const lines = description.replace(/\r/g, "").split("\n");
 | 
				
			||||||
 | 
					    let currentList: Element | null = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (let i = 0; i < lines.length; i++) {
 | 
				
			||||||
 | 
					        const line = lines[i];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (line.includes("New Contributors")) {
 | 
				
			||||||
 | 
					            // we're done, don't parse any more since the new contributors section is the last one
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (line.startsWith("## ")) {
 | 
				
			||||||
 | 
					            const pNode = descriptionNode.ownerDocument.createElement("p");
 | 
				
			||||||
 | 
					            pNode.textContent = line.slice(3);
 | 
				
			||||||
 | 
					            descriptionNode.appendChild(pNode);
 | 
				
			||||||
 | 
					        } else if (line.startsWith("* ")) {
 | 
				
			||||||
 | 
					            const liNode = descriptionNode.ownerDocument.createElement("li");
 | 
				
			||||||
 | 
					            liNode.textContent = line.slice(2).split("in https://github.com")[0].trim(); // don't include links to github
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (!currentList) {
 | 
				
			||||||
 | 
					                currentList = descriptionNode.ownerDocument.createElement("ul");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            currentList.appendChild(liNode);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (currentList && !lines[i + 1].startsWith("* ")) {
 | 
				
			||||||
 | 
					            descriptionNode.appendChild(currentList);
 | 
				
			||||||
 | 
					            currentList = null;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const latestReleaseInformation = await fetch("https://api.github.com/repos/Vencord/Vesktop/releases/latest", {
 | 
				
			||||||
 | 
					    headers: {
 | 
				
			||||||
 | 
					        Accept: "application/vnd.github+json",
 | 
				
			||||||
 | 
					        "X-Github-Api-Version": "2022-11-28"
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}).then(res => res.json());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const metaInfo = await fs.readFile("./meta/dev.vencord.Vesktop.metainfo.xml", "utf-8");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const parser = new DOMParser().parseFromString(metaInfo, "text/xml");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const releaseList = parser.getElementsByTagName("releases")[0];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					for (let i = 0; i < releaseList.childNodes.length; i++) {
 | 
				
			||||||
 | 
					    const release = releaseList.childNodes[i] as Element;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (release.nodeType === 1 && release.getAttribute("version") === latestReleaseInformation.name) {
 | 
				
			||||||
 | 
					        console.log("Latest release already added, nothing to be done");
 | 
				
			||||||
 | 
					        process.exit(0);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const release = parser.createElement("release");
 | 
				
			||||||
 | 
					release.setAttribute("version", latestReleaseInformation.name);
 | 
				
			||||||
 | 
					release.setAttribute("date", latestReleaseInformation.published_at.split("T")[0]);
 | 
				
			||||||
 | 
					release.setAttribute("type", "stable");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const releaseUrl = parser.createElement("url");
 | 
				
			||||||
 | 
					releaseUrl.textContent = latestReleaseInformation.html_url;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					release.appendChild(releaseUrl);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const description = parser.createElement("description");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// we're not using a full markdown parser here since we don't have a lot of formatting options to begin with
 | 
				
			||||||
 | 
					generateDescription(latestReleaseInformation.body, description);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					release.appendChild(description);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					releaseList.insertBefore(release, releaseList.childNodes[0]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const output = xmlFormat(new XMLSerializer().serializeToString(parser), {
 | 
				
			||||||
 | 
					    lineSeparator: "\n",
 | 
				
			||||||
 | 
					    collapseContent: true,
 | 
				
			||||||
 | 
					    indentation: "  "
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					await fs.writeFile("./meta/dev.vencord.Vesktop.metainfo.xml", output, "utf-8");
 | 
				
			||||||
							
								
								
									
										17
									
								
								src/globals.d.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,17 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * SPDX-License-Identifier: GPL-3.0
 | 
				
			||||||
 | 
					 * Modified for Aerocord, originally part of Vesktop, a desktop app aiming to give you a snappier Discord Experience
 | 
				
			||||||
 | 
					 * Copyright (c) 2024-2024 Aiek 
 | 
				
			||||||
 | 
					 * Copyright (c) 2024 RandomServer Community
 | 
				
			||||||
 | 
					 * Copyright (c) 2023 Vendicated and Vencord contributors
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					declare global {
 | 
				
			||||||
 | 
					    export var VesktopNative: typeof import("preload/VesktopNative").VesktopNative;
 | 
				
			||||||
 | 
					    export var Vesktop: typeof import("renderer/index");
 | 
				
			||||||
 | 
					    export var VCDP: any;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    export var IS_DEV: boolean;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export {};
 | 
				
			||||||
							
								
								
									
										30
									
								
								src/main/about.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,30 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * SPDX-License-Identifier: GPL-3.0
 | 
				
			||||||
 | 
					 * Modified for Aerocord, originally part of Vesktop, a desktop app aiming to give you a snappier Discord Experience
 | 
				
			||||||
 | 
					 * Copyright (c) 2024-2024 Aiek 
 | 
				
			||||||
 | 
					 * Copyright (c) 2024 RandomServer Community
 | 
				
			||||||
 | 
					 * Copyright (c) 2023 Vendicated and Vencord contributors
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { BrowserWindow } from "electron";
 | 
				
			||||||
 | 
					import { join } from "path";
 | 
				
			||||||
 | 
					import { ICON_PATH, VIEW_DIR } from "shared/paths";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { makeLinksOpenExternally } from "./utils/makeLinksOpenExternally";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function createAboutWindow() {
 | 
				
			||||||
 | 
					    const about = new BrowserWindow({
 | 
				
			||||||
 | 
					        center: true,
 | 
				
			||||||
 | 
					        autoHideMenuBar: true,
 | 
				
			||||||
 | 
					        icon: ICON_PATH,
 | 
				
			||||||
 | 
					        webPreferences: {
 | 
				
			||||||
 | 
					            preload: join(__dirname, "updaterPreload.js")
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    makeLinksOpenExternally(about);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    about.loadFile(join(VIEW_DIR, "about.html"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return about;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										58
									
								
								src/main/appBadge.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,58 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * SPDX-License-Identifier: GPL-3.0
 | 
				
			||||||
 | 
					 * Modified for Aerocord, originally part of Vesktop, a desktop app aiming to give you a snappier Discord Experience
 | 
				
			||||||
 | 
					 * Copyright (c) 2024-2024 Aiek 
 | 
				
			||||||
 | 
					 * Copyright (c) 2024 RandomServer Community
 | 
				
			||||||
 | 
					 * Copyright (c) 2023 Vendicated and Vencord contributors
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { app, NativeImage, nativeImage } from "electron";
 | 
				
			||||||
 | 
					import { join } from "path";
 | 
				
			||||||
 | 
					import { BADGE_DIR } from "shared/paths";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const imgCache = new Map<number, NativeImage>();
 | 
				
			||||||
 | 
					function loadBadge(index: number) {
 | 
				
			||||||
 | 
					    const cached = imgCache.get(index);
 | 
				
			||||||
 | 
					    if (cached) return cached;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const img = nativeImage.createFromPath(join(BADGE_DIR, `${index}.ico`));
 | 
				
			||||||
 | 
					    imgCache.set(index, img);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return img;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let lastIndex: null | number = -1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function setBadgeCount(count: number) {
 | 
				
			||||||
 | 
					    switch (process.platform) {
 | 
				
			||||||
 | 
					        case "linux":
 | 
				
			||||||
 | 
					            if (count === -1) count = 0;
 | 
				
			||||||
 | 
					            app.setBadgeCount(count);
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					        case "darwin":
 | 
				
			||||||
 | 
					            if (count === 0) {
 | 
				
			||||||
 | 
					                app.dock.setBadge("");
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            app.dock.setBadge(count === -1 ? "•" : count.toString());
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					        case "win32":
 | 
				
			||||||
 | 
					            const [index, description] = getBadgeIndexAndDescription(count);
 | 
				
			||||||
 | 
					            if (lastIndex === index) break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            lastIndex = index;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // circular import shenanigans
 | 
				
			||||||
 | 
					            const { mainWin } = require("./mainWindow") as typeof import("./mainWindow");
 | 
				
			||||||
 | 
					            mainWin.setOverlayIcon(index === null ? null : loadBadge(index), description);
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function getBadgeIndexAndDescription(count: number): [number | null, string] {
 | 
				
			||||||
 | 
					    if (count === -1) return [11, "Unread Messages"];
 | 
				
			||||||
 | 
					    if (count === 0) return [null, "No Notifications"];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const index = Math.max(1, Math.min(count, 10));
 | 
				
			||||||
 | 
					    return [index, `${index} Notification`];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										40
									
								
								src/main/arrpc.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,40 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * SPDX-License-Identifier: GPL-3.0
 | 
				
			||||||
 | 
					 * Modified for Aerocord, originally part of Vesktop, a desktop app aiming to give you a snappier Discord Experience
 | 
				
			||||||
 | 
					 * Copyright (c) 2024-2024 Aiek 
 | 
				
			||||||
 | 
					 * Copyright (c) 2024 RandomServer Community
 | 
				
			||||||
 | 
					 * Copyright (c) 2023 Vendicated and Vencord contributors
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import Server from "arrpc";
 | 
				
			||||||
 | 
					import { IpcEvents } from "shared/IpcEvents";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { mainWin } from "./mainWindow";
 | 
				
			||||||
 | 
					import { Settings } from "./settings";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let server: any;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const inviteCodeRegex = /^(\w|-)+$/;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export async function initArRPC() {
 | 
				
			||||||
 | 
					    if (server || !Settings.store.arRPC) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					        server = await new Server();
 | 
				
			||||||
 | 
					        server.on("activity", (data: any) => mainWin.webContents.send(IpcEvents.ARRPC_ACTIVITY, JSON.stringify(data)));
 | 
				
			||||||
 | 
					        server.on("invite", (invite: string, callback: (valid: boolean) => void) => {
 | 
				
			||||||
 | 
					            invite = String(invite);
 | 
				
			||||||
 | 
					            if (!inviteCodeRegex.test(invite)) return callback(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            mainWin.webContents
 | 
				
			||||||
 | 
					                // Safety: Result of JSON.stringify should always be safe to equal
 | 
				
			||||||
 | 
					                // Also, just to be super super safe, invite is regex validated above
 | 
				
			||||||
 | 
					                .executeJavaScript(`Vesktop.openInviteModal(${JSON.stringify(invite)})`)
 | 
				
			||||||
 | 
					                .then(callback);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					        console.error("Failed to start arRPC server", e);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Settings.addChangeListener("arRPC", initArRPC);
 | 
				
			||||||
							
								
								
									
										59
									
								
								src/main/autoStart.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,59 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * SPDX-License-Identifier: GPL-3.0
 | 
				
			||||||
 | 
					 * Modified for Aerocord, originally part of Vesktop, a desktop app aiming to give you a snappier Discord Experience
 | 
				
			||||||
 | 
					 * Copyright (c) 2024-2024 Aiek 
 | 
				
			||||||
 | 
					 * Copyright (c) 2024 RandomServer Community
 | 
				
			||||||
 | 
					 * Copyright (c) 2023 Vendicated and Vencord contributors
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { app } from "electron";
 | 
				
			||||||
 | 
					import { existsSync, mkdirSync, renameSync, rmSync, writeFileSync } from "fs";
 | 
				
			||||||
 | 
					import { join } from "path";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface AutoStart {
 | 
				
			||||||
 | 
					    isEnabled(): boolean;
 | 
				
			||||||
 | 
					    enable(): void;
 | 
				
			||||||
 | 
					    disable(): void;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function makeAutoStartLinux(): AutoStart {
 | 
				
			||||||
 | 
					    const configDir = process.env.XDG_CONFIG_HOME || join(process.env.HOME!, ".config");
 | 
				
			||||||
 | 
					    const dir = join(configDir, "autostart");
 | 
				
			||||||
 | 
					    const file = join(dir, "vesktop.desktop");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // IM STUPID
 | 
				
			||||||
 | 
					    const legacyName = join(dir, "vencord.desktop");
 | 
				
			||||||
 | 
					    if (existsSync(legacyName)) renameSync(legacyName, file);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // "Quoting must be done by enclosing the argument between double quotes and escaping the double quote character,
 | 
				
			||||||
 | 
					    // backtick character ("`"), dollar sign ("$") and backslash character ("\") by preceding it with an additional backslash character"
 | 
				
			||||||
 | 
					    // https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#exec-variables
 | 
				
			||||||
 | 
					    const commandLine = process.argv.map(arg => '"' + arg.replace(/["$`\\]/g, "\\$&") + '"').join(" ");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					        isEnabled: () => existsSync(file),
 | 
				
			||||||
 | 
					        enable() {
 | 
				
			||||||
 | 
					            const desktopFile = `
 | 
				
			||||||
 | 
					[Desktop Entry]
 | 
				
			||||||
 | 
					Type=Application
 | 
				
			||||||
 | 
					Name=Vesktop
 | 
				
			||||||
 | 
					Comment=Vesktop autostart script
 | 
				
			||||||
 | 
					Exec=${commandLine}
 | 
				
			||||||
 | 
					StartupNotify=false
 | 
				
			||||||
 | 
					Terminal=false
 | 
				
			||||||
 | 
					`.trim();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            mkdirSync(dir, { recursive: true });
 | 
				
			||||||
 | 
					            writeFileSync(file, desktopFile);
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        disable: () => rmSync(file, { force: true })
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const autoStartWindowsMac: AutoStart = {
 | 
				
			||||||
 | 
					    isEnabled: () => app.getLoginItemSettings().openAtLogin,
 | 
				
			||||||
 | 
					    enable: () => app.setLoginItemSettings({ openAtLogin: true }),
 | 
				
			||||||
 | 
					    disable: () => app.setLoginItemSettings({ openAtLogin: false })
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const autoStart = process.platform === "linux" ? makeAutoStartLinux() : autoStartWindowsMac;
 | 
				
			||||||
							
								
								
									
										78
									
								
								src/main/constants.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,78 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * SPDX-License-Identifier: GPL-3.0
 | 
				
			||||||
 | 
					 * Modified for Aerocord, originally part of Vesktop, a desktop app aiming to give you a snappier Discord Experience
 | 
				
			||||||
 | 
					 * Copyright (c) 2024-2024 Aiek 
 | 
				
			||||||
 | 
					 * Copyright (c) 2024 RandomServer Community
 | 
				
			||||||
 | 
					 * Copyright (c) 2023 Vendicated and Vencord contributors
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { app } from "electron";
 | 
				
			||||||
 | 
					import { existsSync, mkdirSync, readdirSync, renameSync, rmdirSync } from "fs";
 | 
				
			||||||
 | 
					import { dirname, join } from "path";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const vesktopDir = dirname(process.execPath);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const PORTABLE =
 | 
				
			||||||
 | 
					    process.platform === "win32" &&
 | 
				
			||||||
 | 
					    !process.execPath.toLowerCase().endsWith("electron.exe") &&
 | 
				
			||||||
 | 
					    !existsSync(join(vesktopDir, "Uninstall Vesktop.exe"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const LEGACY_DATA_DIR = join(app.getPath("appData"), "VencordDesktop", "VencordDesktop");
 | 
				
			||||||
 | 
					export const DATA_DIR =
 | 
				
			||||||
 | 
					    process.env.VENCORD_USER_DATA_DIR || (PORTABLE ? join(vesktopDir, "Aerocord_Data") : join(app.getPath("userData")));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					mkdirSync(DATA_DIR, { recursive: true });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TODO: remove eventually
 | 
				
			||||||
 | 
					if (existsSync(LEGACY_DATA_DIR)) {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					        console.warn("Detected legacy settings dir", LEGACY_DATA_DIR + ".", "migrating to", DATA_DIR);
 | 
				
			||||||
 | 
					        for (const file of readdirSync(LEGACY_DATA_DIR)) {
 | 
				
			||||||
 | 
					            renameSync(join(LEGACY_DATA_DIR, file), join(DATA_DIR, file));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        rmdirSync(LEGACY_DATA_DIR);
 | 
				
			||||||
 | 
					        renameSync(
 | 
				
			||||||
 | 
					            join(app.getPath("appData"), "VencordDesktop", "IndexedDB"),
 | 
				
			||||||
 | 
					            join(DATA_DIR, "sessionData", "IndexedDB")
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					        console.error("Migration failed", e);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					const SESSION_DATA_DIR = join(DATA_DIR, "sessionData");
 | 
				
			||||||
 | 
					app.setPath("sessionData", SESSION_DATA_DIR);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const VENCORD_SETTINGS_DIR = join(DATA_DIR, "settings");
 | 
				
			||||||
 | 
					export const VENCORD_QUICKCSS_FILE = join(VENCORD_SETTINGS_DIR, "quickCss.css");
 | 
				
			||||||
 | 
					export const VENCORD_SETTINGS_FILE = join(VENCORD_SETTINGS_DIR, "settings.json");
 | 
				
			||||||
 | 
					export const VENCORD_THEMES_DIR = join(DATA_DIR, "themes");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// needs to be inline require because of circular dependency
 | 
				
			||||||
 | 
					// as otherwise "DATA_DIR" (which is used by ./settings) will be uninitialised
 | 
				
			||||||
 | 
					export const VENCORD_FILES_DIR =
 | 
				
			||||||
 | 
					    (require("./settings") as typeof import("./settings")).State.store.vencordDir ||
 | 
				
			||||||
 | 
					    join(SESSION_DATA_DIR, "vencordFiles");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const USER_AGENT = `Vesktop/${app.getVersion()} (https://github.com/Vencord/Vesktop)`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// dimensions shamelessly stolen from Discord Desktop :3
 | 
				
			||||||
 | 
					export const MIN_WIDTH = 940;
 | 
				
			||||||
 | 
					export const MIN_HEIGHT = 500;
 | 
				
			||||||
 | 
					export const DEFAULT_WIDTH = 1280;
 | 
				
			||||||
 | 
					export const DEFAULT_HEIGHT = 720;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const DISCORD_HOSTNAMES = ["discord.com", "canary.discord.com", "ptb.discord.com"];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const VersionString = `AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${process.versions.chrome.split(".")[0]}.0.0.0 Safari/537.36`;
 | 
				
			||||||
 | 
					const BrowserUserAgents = {
 | 
				
			||||||
 | 
					    darwin: `Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) ${VersionString}`,
 | 
				
			||||||
 | 
					    linux: `Mozilla/5.0 (X11; Linux x86_64) ${VersionString}`,
 | 
				
			||||||
 | 
					    windows: `Mozilla/5.0 (Windows NT 10.0; Win64; x64) ${VersionString}`
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const BrowserUserAgent = BrowserUserAgents[process.platform] || BrowserUserAgents.windows;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const enum MessageBoxChoice {
 | 
				
			||||||
 | 
					    Default,
 | 
				
			||||||
 | 
					    Cancel
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										76
									
								
								src/main/firstLaunch.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,76 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * SPDX-License-Identifier: GPL-3.0
 | 
				
			||||||
 | 
					 * Modified for Aerocord, originally part of Vesktop, a desktop app aiming to give you a snappier Discord Experience
 | 
				
			||||||
 | 
					 * Copyright (c) 2024-2024 Aiek 
 | 
				
			||||||
 | 
					 * Copyright (c) 2024 RandomServer Community
 | 
				
			||||||
 | 
					 * Copyright (c) 2023 Vendicated and Vencord contributors
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { app } from "electron";
 | 
				
			||||||
 | 
					import { BrowserWindow } from "electron/main";
 | 
				
			||||||
 | 
					import { copyFileSync, mkdirSync, readdirSync } from "fs";
 | 
				
			||||||
 | 
					import { join } from "path";
 | 
				
			||||||
 | 
					import { SplashProps } from "shared/browserWinProperties";
 | 
				
			||||||
 | 
					import { ICON_PATH, VIEW_DIR } from "shared/paths";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { autoStart } from "./autoStart";
 | 
				
			||||||
 | 
					import { DATA_DIR } from "./constants";
 | 
				
			||||||
 | 
					import { createWindows } from "./mainWindow";
 | 
				
			||||||
 | 
					import { Settings, State } from "./settings";
 | 
				
			||||||
 | 
					import { makeLinksOpenExternally } from "./utils/makeLinksOpenExternally";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface Data {
 | 
				
			||||||
 | 
					    discordBranch: "stable" | "canary" | "ptb";
 | 
				
			||||||
 | 
					    minimizeToTray?: "on";
 | 
				
			||||||
 | 
					    autoStart?: "on";
 | 
				
			||||||
 | 
					    importSettings?: "on";
 | 
				
			||||||
 | 
					    richPresence?: "on";
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function createFirstLaunchTour() {
 | 
				
			||||||
 | 
					    const win = new BrowserWindow({
 | 
				
			||||||
 | 
					        ...SplashProps,
 | 
				
			||||||
 | 
					        frame: true,
 | 
				
			||||||
 | 
					        autoHideMenuBar: true,
 | 
				
			||||||
 | 
					        height: 470,
 | 
				
			||||||
 | 
					        width: 550,
 | 
				
			||||||
 | 
					        icon: ICON_PATH
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    makeLinksOpenExternally(win);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    win.loadFile(join(VIEW_DIR, "first-launch.html"));
 | 
				
			||||||
 | 
					    win.webContents.addListener("console-message", (_e, _l, msg) => {
 | 
				
			||||||
 | 
					        if (msg === "cancel") return app.exit();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!msg.startsWith("form:")) return;
 | 
				
			||||||
 | 
					        const data = JSON.parse(msg.slice(5)) as Data;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        console.log(data);
 | 
				
			||||||
 | 
					        State.store.firstLaunch = false;
 | 
				
			||||||
 | 
					        Settings.store.discordBranch = data.discordBranch;
 | 
				
			||||||
 | 
					        Settings.store.minimizeToTray = !!data.minimizeToTray;
 | 
				
			||||||
 | 
					        Settings.store.arRPC = !!data.richPresence;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (data.autoStart) autoStart.enable();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (data.importSettings) {
 | 
				
			||||||
 | 
					            const from = join(app.getPath("userData"), "..", "Vencord", "settings");
 | 
				
			||||||
 | 
					            const to = join(DATA_DIR, "settings");
 | 
				
			||||||
 | 
					            try {
 | 
				
			||||||
 | 
					                const files = readdirSync(from);
 | 
				
			||||||
 | 
					                mkdirSync(to, { recursive: true });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                for (const file of files) {
 | 
				
			||||||
 | 
					                    copyFileSync(join(from, file), join(to, file));
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            } catch (e) {
 | 
				
			||||||
 | 
					                console.error("Failed to import settings:", e);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        win.close();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        createWindows();
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										117
									
								
								src/main/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,117 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * SPDX-License-Identifier: GPL-3.0
 | 
				
			||||||
 | 
					 * Modified for Aerocord, originally part of Vesktop, a desktop app aiming to give you a snappier Discord Experience
 | 
				
			||||||
 | 
					 * Copyright (c) 2024-2024 Aiek 
 | 
				
			||||||
 | 
					 * Copyright (c) 2024 RandomServer Community
 | 
				
			||||||
 | 
					 * Copyright (c) 2023 Vendicated and Vencord contributors
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import "./ipc";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { app, BrowserWindow, nativeTheme } from "electron";
 | 
				
			||||||
 | 
					import { checkUpdates } from "updater/main";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { DATA_DIR } from "./constants";
 | 
				
			||||||
 | 
					import { createFirstLaunchTour } from "./firstLaunch";
 | 
				
			||||||
 | 
					import { createWindows, mainWin } from "./mainWindow";
 | 
				
			||||||
 | 
					import { registerMediaPermissionsHandler } from "./mediaPermissions";
 | 
				
			||||||
 | 
					import { registerScreenShareHandler } from "./screenShare";
 | 
				
			||||||
 | 
					import { Settings, State } from "./settings";
 | 
				
			||||||
 | 
					import { isDeckGameMode } from "./utils/steamOS";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if (IS_DEV) {
 | 
				
			||||||
 | 
					    require("source-map-support").install();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					process.env.VENCORD_USER_DATA_DIR = DATA_DIR;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function init() {
 | 
				
			||||||
 | 
					    const { disableSmoothScroll, hardwareAcceleration } = Settings.store;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const enabledFeatures = app.commandLine.getSwitchValue("enable-features").split(",");
 | 
				
			||||||
 | 
					    const disabledFeatures = app.commandLine.getSwitchValue("disable-features").split(",");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (hardwareAcceleration === false) {
 | 
				
			||||||
 | 
					        app.disableHardwareAcceleration();
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        enabledFeatures.push("VaapiVideoDecodeLinuxGL", "VaapiVideoEncoder", "VaapiVideoDecoder");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (disableSmoothScroll) {
 | 
				
			||||||
 | 
					        app.commandLine.appendSwitch("disable-smooth-scrolling");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // disable renderer backgrounding to prevent the app from unloading when in the background
 | 
				
			||||||
 | 
					    // https://github.com/electron/electron/issues/2822
 | 
				
			||||||
 | 
					    // https://github.com/GoogleChrome/chrome-launcher/blob/5a27dd574d47a75fec0fb50f7b774ebf8a9791ba/docs/chrome-flags-for-tools.md#task-throttling
 | 
				
			||||||
 | 
					    app.commandLine.appendSwitch("disable-renderer-backgrounding");
 | 
				
			||||||
 | 
					    app.commandLine.appendSwitch("disable-background-timer-throttling");
 | 
				
			||||||
 | 
					    app.commandLine.appendSwitch("disable-backgrounding-occluded-windows");
 | 
				
			||||||
 | 
					    if (process.platform === "win32") {
 | 
				
			||||||
 | 
					        disabledFeatures.push("CalculateNativeWinOcclusion");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // work around chrome 66 disabling autoplay by default
 | 
				
			||||||
 | 
					    app.commandLine.appendSwitch("autoplay-policy", "no-user-gesture-required");
 | 
				
			||||||
 | 
					    // WinRetrieveSuggestionsOnlyOnDemand: Work around electron 13 bug w/ async spellchecking on Windows.
 | 
				
			||||||
 | 
					    // HardwareMediaKeyHandling,MediaSessionService: Prevent Discord from registering as a media service.
 | 
				
			||||||
 | 
					    //
 | 
				
			||||||
 | 
					    // WidgetLayering (Vencord Added): Fix DevTools context menus https://github.com/electron/electron/issues/38790
 | 
				
			||||||
 | 
					    disabledFeatures.push("WinRetrieveSuggestionsOnlyOnDemand", "HardwareMediaKeyHandling", "MediaSessionService");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Support TTS on Linux using speech-dispatcher
 | 
				
			||||||
 | 
					    app.commandLine.appendSwitch("enable-speech-dispatcher");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    app.commandLine.appendSwitch("enable-features", [...new Set(enabledFeatures)].filter(Boolean).join(","));
 | 
				
			||||||
 | 
					    app.commandLine.appendSwitch("disable-features", [...new Set(disabledFeatures)].filter(Boolean).join(","));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // In the Flatpak on SteamOS the theme is detected as light, but SteamOS only has a dark mode, so we just override it
 | 
				
			||||||
 | 
					    if (isDeckGameMode) nativeTheme.themeSource = "dark";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    app.on("second-instance", (_event, _cmdLine, _cwd, data: any) => {
 | 
				
			||||||
 | 
					        if (data.IS_DEV) app.quit();
 | 
				
			||||||
 | 
					        else if (mainWin) {
 | 
				
			||||||
 | 
					            if (mainWin.isMinimized()) mainWin.restore();
 | 
				
			||||||
 | 
					            if (!mainWin.isVisible()) mainWin.show();
 | 
				
			||||||
 | 
					            mainWin.focus();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    app.whenReady().then(async () => {
 | 
				
			||||||
 | 
					        checkUpdates();
 | 
				
			||||||
 | 
					        if (process.platform === "win32") app.setAppUserModelId("dev.vencord.vesktop");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        registerScreenShareHandler();
 | 
				
			||||||
 | 
					        registerMediaPermissionsHandler();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        bootstrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        app.on("activate", () => {
 | 
				
			||||||
 | 
					            if (BrowserWindow.getAllWindows().length === 0) createWindows();
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if (!app.requestSingleInstanceLock({ IS_DEV })) {
 | 
				
			||||||
 | 
					    if (IS_DEV) {
 | 
				
			||||||
 | 
					        console.log("Vesktop is already running. Quitting previous instance...");
 | 
				
			||||||
 | 
					        init();
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        console.log("Vesktop is already running. Quitting...");
 | 
				
			||||||
 | 
					        app.quit();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					} else {
 | 
				
			||||||
 | 
					    init();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async function bootstrap() {
 | 
				
			||||||
 | 
					    if (!Object.hasOwn(State.store, "firstLaunch")) {
 | 
				
			||||||
 | 
					        createFirstLaunchTour();
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        createWindows();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					app.on("window-all-closed", () => {
 | 
				
			||||||
 | 
					    if (process.platform !== "darwin") app.quit();
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										168
									
								
								src/main/ipc.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,168 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * SPDX-License-Identifier: GPL-3.0
 | 
				
			||||||
 | 
					 * Modified for Aerocord, originally part of Vesktop, a desktop app aiming to give you a snappier Discord Experience
 | 
				
			||||||
 | 
					 * Copyright (c) 2024-2024 Aiek 
 | 
				
			||||||
 | 
					 * Copyright (c) 2024 RandomServer Community
 | 
				
			||||||
 | 
					 * Copyright (c) 2023 Vendicated and Vencord contributors
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if (process.platform === "linux") import("./venmic");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { execFile } from "child_process";
 | 
				
			||||||
 | 
					import { app, BrowserWindow, clipboard, dialog, nativeImage, RelaunchOptions, session, shell } from "electron";
 | 
				
			||||||
 | 
					import { mkdirSync, readFileSync, watch } from "fs";
 | 
				
			||||||
 | 
					import { open, readFile } from "fs/promises";
 | 
				
			||||||
 | 
					import { release } from "os";
 | 
				
			||||||
 | 
					import { join } from "path";
 | 
				
			||||||
 | 
					import { debounce } from "shared/utils/debounce";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { IpcEvents } from "../shared/IpcEvents";
 | 
				
			||||||
 | 
					import { setBadgeCount } from "./appBadge";
 | 
				
			||||||
 | 
					import { autoStart } from "./autoStart";
 | 
				
			||||||
 | 
					import { VENCORD_FILES_DIR, VENCORD_QUICKCSS_FILE, VENCORD_THEMES_DIR } from "./constants";
 | 
				
			||||||
 | 
					import { mainWin } from "./mainWindow";
 | 
				
			||||||
 | 
					import { Settings, State } from "./settings";
 | 
				
			||||||
 | 
					import { handle, handleSync } from "./utils/ipcWrappers";
 | 
				
			||||||
 | 
					import { PopoutWindows } from "./utils/popout";
 | 
				
			||||||
 | 
					import { isDeckGameMode, showGamePage } from "./utils/steamOS";
 | 
				
			||||||
 | 
					import { isValidVencordInstall } from "./utils/vencordLoader";
 | 
				
			||||||
 | 
					import { isvencorddisabled } from "main/mainWindow";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if (!isvencorddisabled) {
 | 
				
			||||||
 | 
					    handleSync(IpcEvents.GET_VENCORD_PRELOAD_FILE, () => join(VENCORD_FILES_DIR, "vencordDesktopPreload.js"));
 | 
				
			||||||
 | 
					} else {                                                                            
 | 
				
			||||||
 | 
					    handleSync(IpcEvents.GET_VENCORD_PRELOAD_FILE, () => join(VENCORD_FILES_DIR, "vencordDesktopPreload1.js"));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					handleSync(IpcEvents.GET_VENCORD_RENDERER_SCRIPT, () =>
 | 
				
			||||||
 | 
					    readFileSync(join(VENCORD_FILES_DIR, "vencordDesktopRenderer.js"), "utf-8")
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					handleSync(IpcEvents.GET_RENDERER_SCRIPT, () => readFileSync(join(__dirname, "renderer.js"), "utf-8"));
 | 
				
			||||||
 | 
					handleSync(IpcEvents.GET_RENDERER_CSS_FILE, () => join(__dirname, "renderer.css"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					handleSync(IpcEvents.GET_SETTINGS, () => Settings.plain);
 | 
				
			||||||
 | 
					handleSync(IpcEvents.GET_VERSION, () => app.getVersion());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					handleSync(
 | 
				
			||||||
 | 
					    IpcEvents.SUPPORTS_WINDOWS_TRANSPARENCY,
 | 
				
			||||||
 | 
					    () => process.platform === "win32" && Number(release().split(".").pop()) >= 22621
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					handleSync(IpcEvents.AUTOSTART_ENABLED, () => autoStart.isEnabled());
 | 
				
			||||||
 | 
					handle(IpcEvents.ENABLE_AUTOSTART, autoStart.enable);
 | 
				
			||||||
 | 
					handle(IpcEvents.DISABLE_AUTOSTART, autoStart.disable);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					handle(IpcEvents.SET_SETTINGS, (_, settings: typeof Settings.store, path?: string) => {
 | 
				
			||||||
 | 
					    Settings.setData(settings, path);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					handle(IpcEvents.RELAUNCH, async () => {
 | 
				
			||||||
 | 
					    const options: RelaunchOptions = {
 | 
				
			||||||
 | 
					        args: process.argv.slice(1).concat(["--relaunch"])
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    if (isDeckGameMode) {
 | 
				
			||||||
 | 
					        // We can't properly relaunch when running under gamescope, but we can at least navigate to our page in Steam.
 | 
				
			||||||
 | 
					        await showGamePage();
 | 
				
			||||||
 | 
					    } else if (app.isPackaged && process.env.APPIMAGE) {
 | 
				
			||||||
 | 
					        execFile(process.env.APPIMAGE, options.args);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        app.relaunch(options);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    app.exit();
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					handle(IpcEvents.SHOW_ITEM_IN_FOLDER, (_, path) => {
 | 
				
			||||||
 | 
					    shell.showItemInFolder(path);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					handle(IpcEvents.FOCUS, () => {
 | 
				
			||||||
 | 
					    mainWin.show();
 | 
				
			||||||
 | 
					    mainWin.setSkipTaskbar(false);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					handle(IpcEvents.CLOSE, (e, key?: string) => {
 | 
				
			||||||
 | 
					    const popout = PopoutWindows.get(key!);
 | 
				
			||||||
 | 
					    if (popout) return popout.close();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const win = BrowserWindow.fromWebContents(e.sender) ?? e.sender;
 | 
				
			||||||
 | 
					    win.close();
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					handle(IpcEvents.MINIMIZE, e => {
 | 
				
			||||||
 | 
					    mainWin.minimize();
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					handle(IpcEvents.MAXIMIZE, e => {
 | 
				
			||||||
 | 
					    if (mainWin.isMaximized()) {
 | 
				
			||||||
 | 
					        mainWin.unmaximize();
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        mainWin.maximize();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					handleSync(IpcEvents.SPELLCHECK_GET_AVAILABLE_LANGUAGES, e => {
 | 
				
			||||||
 | 
					    e.returnValue = session.defaultSession.availableSpellCheckerLanguages;
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					handle(IpcEvents.SPELLCHECK_REPLACE_MISSPELLING, (e, word: string) => {
 | 
				
			||||||
 | 
					    e.sender.replaceMisspelling(word);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					handle(IpcEvents.SPELLCHECK_ADD_TO_DICTIONARY, (e, word: string) => {
 | 
				
			||||||
 | 
					    e.sender.session.addWordToSpellCheckerDictionary(word);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					handleSync(IpcEvents.GET_VENCORD_DIR, e => (e.returnValue = State.store.vencordDir));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					handle(IpcEvents.SELECT_VENCORD_DIR, async (_e, value?: null) => {
 | 
				
			||||||
 | 
					    if (value === null) {
 | 
				
			||||||
 | 
					        delete State.store.vencordDir;
 | 
				
			||||||
 | 
					        return "ok";
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const res = await dialog.showOpenDialog(mainWin!, {
 | 
				
			||||||
 | 
					        properties: ["openDirectory"]
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    if (!res.filePaths.length) return "cancelled";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const dir = res.filePaths[0];
 | 
				
			||||||
 | 
					    if (!isValidVencordInstall(dir)) return "invalid";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    State.store.vencordDir = dir;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return "ok";
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					handle(IpcEvents.SET_BADGE_COUNT, (_, count: number) => setBadgeCount(count));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					handle(IpcEvents.CLIPBOARD_COPY_IMAGE, async (_, buf: ArrayBuffer, src: string) => {
 | 
				
			||||||
 | 
					    clipboard.write({
 | 
				
			||||||
 | 
					        html: `<img src="${src.replaceAll('"', '\\"')}">`,
 | 
				
			||||||
 | 
					        image: nativeImage.createFromBuffer(Buffer.from(buf))
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function readCss() {
 | 
				
			||||||
 | 
					    return readFile(VENCORD_QUICKCSS_FILE, "utf-8").catch(() => "");
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					open(VENCORD_QUICKCSS_FILE, "a+").then(fd => {
 | 
				
			||||||
 | 
					    fd.close();
 | 
				
			||||||
 | 
					    watch(
 | 
				
			||||||
 | 
					        VENCORD_QUICKCSS_FILE,
 | 
				
			||||||
 | 
					        { persistent: false },
 | 
				
			||||||
 | 
					        debounce(async () => {
 | 
				
			||||||
 | 
					            mainWin?.webContents.postMessage("VencordQuickCssUpdate", await readCss());
 | 
				
			||||||
 | 
					        }, 50)
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					mkdirSync(VENCORD_THEMES_DIR, { recursive: true });
 | 
				
			||||||
 | 
					watch(
 | 
				
			||||||
 | 
					    VENCORD_THEMES_DIR,
 | 
				
			||||||
 | 
					    { persistent: false },
 | 
				
			||||||
 | 
					    debounce(() => {
 | 
				
			||||||
 | 
					        mainWin?.webContents.postMessage("VencordThemeUpdate", void 0);
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
							
								
								
									
										534
									
								
								src/main/mainWindow.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,534 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * SPDX-License-Identifier: GPL-3.0
 | 
				
			||||||
 | 
					 * Modified for Aerocord, originally part of Vesktop, a desktop app aiming to give you a snappier Discord Experience
 | 
				
			||||||
 | 
					 * Copyright (c) 2024-2024 Aiek 
 | 
				
			||||||
 | 
					 * Copyright (c) 2024 RandomServer Community
 | 
				
			||||||
 | 
					 * Copyright (c) 2023 Vendicated and Vencord contributors
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					    app,
 | 
				
			||||||
 | 
					    BrowserWindow,
 | 
				
			||||||
 | 
					    BrowserWindowConstructorOptions,
 | 
				
			||||||
 | 
					    dialog,
 | 
				
			||||||
 | 
					    Menu,
 | 
				
			||||||
 | 
					    MenuItemConstructorOptions,
 | 
				
			||||||
 | 
					    nativeTheme,
 | 
				
			||||||
 | 
					    screen,
 | 
				
			||||||
 | 
					    shell,
 | 
				
			||||||
 | 
					    session,
 | 
				
			||||||
 | 
					    Tray
 | 
				
			||||||
 | 
					} from "electron";
 | 
				
			||||||
 | 
					import { rm } from "fs/promises";
 | 
				
			||||||
 | 
					import { join } from "path";
 | 
				
			||||||
 | 
					import { IpcEvents } from "shared/IpcEvents";
 | 
				
			||||||
 | 
					import { isTruthy } from "shared/utils/guards";
 | 
				
			||||||
 | 
					import { once } from "shared/utils/once";
 | 
				
			||||||
 | 
					import type { SettingsStore } from "shared/utils/SettingsStore";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { ICON_PATH } from "../shared/paths";
 | 
				
			||||||
 | 
					import { createAboutWindow } from "./about";
 | 
				
			||||||
 | 
					import { initArRPC } from "./arrpc";
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					    BrowserUserAgent,
 | 
				
			||||||
 | 
					    DATA_DIR,
 | 
				
			||||||
 | 
					    DEFAULT_HEIGHT,
 | 
				
			||||||
 | 
					    DEFAULT_WIDTH,
 | 
				
			||||||
 | 
					    MessageBoxChoice,
 | 
				
			||||||
 | 
					    MIN_HEIGHT,
 | 
				
			||||||
 | 
					    MIN_WIDTH,
 | 
				
			||||||
 | 
					    VENCORD_FILES_DIR
 | 
				
			||||||
 | 
					} from "./constants";
 | 
				
			||||||
 | 
					import { Settings, State, VencordSettings } from "./settings";
 | 
				
			||||||
 | 
					import { createSplashWindow } from "./splash";
 | 
				
			||||||
 | 
					import { makeLinksOpenExternally } from "./utils/makeLinksOpenExternally";
 | 
				
			||||||
 | 
					import { applyDeckKeyboardFix, askToApplySteamLayout, isDeckGameMode } from "./utils/steamOS";
 | 
				
			||||||
 | 
					import { downloadVencordFiles, ensureVencordFiles } from "./utils/vencordLoader";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let isQuitting = false;
 | 
				
			||||||
 | 
					let tray: Tray;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					applyDeckKeyboardFix();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					app.on("before-quit", () => {
 | 
				
			||||||
 | 
					    isQuitting = true;
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export let mainWin: BrowserWindow;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function makeSettingsListenerHelpers<O extends object>(o: SettingsStore<O>) {
 | 
				
			||||||
 | 
					    const listeners = new Map<(data: any) => void, PropertyKey>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const addListener: typeof o.addChangeListener = (path, cb) => {
 | 
				
			||||||
 | 
					        listeners.set(cb, path);
 | 
				
			||||||
 | 
					        o.addChangeListener(path, cb);
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    const removeAllListeners = () => {
 | 
				
			||||||
 | 
					        for (const [listener, path] of listeners) {
 | 
				
			||||||
 | 
					            o.removeChangeListener(path as any, listener);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        listeners.clear();
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return [addListener, removeAllListeners] as const;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const [addSettingsListener, removeSettingsListeners] = makeSettingsListenerHelpers(Settings);
 | 
				
			||||||
 | 
					const [addVencordSettingsListener, removeVencordSettingsListeners] = makeSettingsListenerHelpers(VencordSettings);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function initTray(win: BrowserWindow) {
 | 
				
			||||||
 | 
					    const onTrayClick = () => {
 | 
				
			||||||
 | 
					        if (Settings.store.clickTrayToShowHide && win.isVisible()) win.hide();
 | 
				
			||||||
 | 
					        else win.show();
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    const trayMenu = Menu.buildFromTemplate([
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            label: "Open",
 | 
				
			||||||
 | 
					            click() {
 | 
				
			||||||
 | 
					                win.show();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            label: "About",
 | 
				
			||||||
 | 
					            click: createAboutWindow
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            label: "Repair Vencord",
 | 
				
			||||||
 | 
					            async click() {
 | 
				
			||||||
 | 
					                await downloadVencordFiles();
 | 
				
			||||||
 | 
					                app.relaunch();
 | 
				
			||||||
 | 
					                app.quit();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            label: "Reset User Data",
 | 
				
			||||||
 | 
					            async click() {
 | 
				
			||||||
 | 
					                await clearData(win);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            type: "separator"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            label: "Open Updater",
 | 
				
			||||||
 | 
					            click() {
 | 
				
			||||||
 | 
					                const updaterPath = join(app.getPath('exe'), '..', 'Updater.exe');
 | 
				
			||||||
 | 
					                shell.openPath(updaterPath);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            label: "Restart",
 | 
				
			||||||
 | 
					            click() {
 | 
				
			||||||
 | 
					                app.relaunch();
 | 
				
			||||||
 | 
					                app.quit();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            label: "Close",
 | 
				
			||||||
 | 
					            click() {
 | 
				
			||||||
 | 
					                isQuitting = true;
 | 
				
			||||||
 | 
					                app.quit();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    tray = new Tray(ICON_PATH);
 | 
				
			||||||
 | 
					    tray.setToolTip("Aerocord");
 | 
				
			||||||
 | 
					    tray.setContextMenu(trayMenu);
 | 
				
			||||||
 | 
					    tray.on("click", onTrayClick);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async function clearData(win: BrowserWindow) {
 | 
				
			||||||
 | 
					    const { response } = await dialog.showMessageBox(win, {
 | 
				
			||||||
 | 
					        message: "Are you sure you want to reset Aerocord?",
 | 
				
			||||||
 | 
					        detail: "This will log you out, clear caches and reset all your settings!\n\Aerocord will automatically restart after this operation.",
 | 
				
			||||||
 | 
					        buttons: ["Yes", "No"],
 | 
				
			||||||
 | 
					        cancelId: MessageBoxChoice.Cancel,
 | 
				
			||||||
 | 
					        defaultId: MessageBoxChoice.Default,
 | 
				
			||||||
 | 
					        type: "warning"
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (response === MessageBoxChoice.Cancel) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    win.close();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await win.webContents.session.clearStorageData();
 | 
				
			||||||
 | 
					    await win.webContents.session.clearCache();
 | 
				
			||||||
 | 
					    await win.webContents.session.clearCodeCaches({});
 | 
				
			||||||
 | 
					    await rm(DATA_DIR, { force: true, recursive: true });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    app.relaunch();
 | 
				
			||||||
 | 
					    app.quit();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type MenuItemList = Array<MenuItemConstructorOptions | false>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function initMenuBar(win: BrowserWindow) {
 | 
				
			||||||
 | 
					    const isWindows = process.platform === "win32";
 | 
				
			||||||
 | 
					    const isDarwin = process.platform === "darwin";
 | 
				
			||||||
 | 
					    const wantCtrlQ = !isWindows || VencordSettings.store.winCtrlQ;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const subMenu = [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            label: "About Vesktop",
 | 
				
			||||||
 | 
					            click: createAboutWindow
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            label: "Force Update Vencord",
 | 
				
			||||||
 | 
					            async click() {
 | 
				
			||||||
 | 
					                await downloadVencordFiles();
 | 
				
			||||||
 | 
					                app.relaunch();
 | 
				
			||||||
 | 
					                app.quit();
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            toolTip: "Vesktop will automatically restart after this operation"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            label: "Reset Aerocord",
 | 
				
			||||||
 | 
					            async click() {
 | 
				
			||||||
 | 
					                await clearData(win);
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            toolTip: "Vesktop will automatically restart after this operation"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            label: "Relaunch",
 | 
				
			||||||
 | 
					            accelerator: "CmdOrCtrl+Shift+R",
 | 
				
			||||||
 | 
					            click() {
 | 
				
			||||||
 | 
					                app.relaunch();
 | 
				
			||||||
 | 
					                app.quit();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        ...(!isDarwin
 | 
				
			||||||
 | 
					            ? []
 | 
				
			||||||
 | 
					            : ([
 | 
				
			||||||
 | 
					                  {
 | 
				
			||||||
 | 
					                      type: "separator"
 | 
				
			||||||
 | 
					                  },
 | 
				
			||||||
 | 
					                  {
 | 
				
			||||||
 | 
					                      label: "Settings",
 | 
				
			||||||
 | 
					                      accelerator: "CmdOrCtrl+,",
 | 
				
			||||||
 | 
					                      async click() {
 | 
				
			||||||
 | 
					                          mainWin.webContents.executeJavaScript(
 | 
				
			||||||
 | 
					                              "Vencord.Webpack.Common.SettingsRouter.open('My Account')"
 | 
				
			||||||
 | 
					                          );
 | 
				
			||||||
 | 
					                      }
 | 
				
			||||||
 | 
					                  },
 | 
				
			||||||
 | 
					                  {
 | 
				
			||||||
 | 
					                      type: "separator"
 | 
				
			||||||
 | 
					                  },
 | 
				
			||||||
 | 
					                  {
 | 
				
			||||||
 | 
					                      role: "hide"
 | 
				
			||||||
 | 
					                  },
 | 
				
			||||||
 | 
					                  {
 | 
				
			||||||
 | 
					                      role: "hideOthers"
 | 
				
			||||||
 | 
					                  },
 | 
				
			||||||
 | 
					                  {
 | 
				
			||||||
 | 
					                      role: "unhide"
 | 
				
			||||||
 | 
					                  },
 | 
				
			||||||
 | 
					                  {
 | 
				
			||||||
 | 
					                      type: "separator"
 | 
				
			||||||
 | 
					                  }
 | 
				
			||||||
 | 
					              ] satisfies MenuItemList)),
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            label: "Quit",
 | 
				
			||||||
 | 
					            accelerator: wantCtrlQ ? "CmdOrCtrl+Q" : void 0,
 | 
				
			||||||
 | 
					            visible: !isWindows,
 | 
				
			||||||
 | 
					            role: "quit",
 | 
				
			||||||
 | 
					            click() {
 | 
				
			||||||
 | 
					                app.quit();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        isWindows && {
 | 
				
			||||||
 | 
					            label: "Quit",
 | 
				
			||||||
 | 
					            accelerator: "Alt+F4",
 | 
				
			||||||
 | 
					            role: "quit",
 | 
				
			||||||
 | 
					            click() {
 | 
				
			||||||
 | 
					                app.quit();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        // See https://github.com/electron/electron/issues/14742 and https://github.com/electron/electron/issues/5256
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            label: "Zoom in (hidden, hack for Qwertz and others)",
 | 
				
			||||||
 | 
					            accelerator: "CmdOrCtrl+=",
 | 
				
			||||||
 | 
					            role: "zoomIn",
 | 
				
			||||||
 | 
					            visible: false
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    ] satisfies MenuItemList;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const menu = Menu.buildFromTemplate([
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            label: "Vesktop",
 | 
				
			||||||
 | 
					            role: "appMenu",
 | 
				
			||||||
 | 
					            submenu: subMenu.filter(isTruthy)
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        { role: "fileMenu" },
 | 
				
			||||||
 | 
					        { role: "editMenu" },
 | 
				
			||||||
 | 
					        { role: "viewMenu" },
 | 
				
			||||||
 | 
					        { role: "windowMenu" }
 | 
				
			||||||
 | 
					    ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Menu.setApplicationMenu(menu);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function getWindowBoundsOptions(): BrowserWindowConstructorOptions {
 | 
				
			||||||
 | 
					    // We want the default window behaivour to apply in game mode since it expects everything to be fullscreen and maximized.
 | 
				
			||||||
 | 
					    if (isDeckGameMode) return {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const { x, y, width, height } = State.store.windowBounds ?? {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const options = {
 | 
				
			||||||
 | 
					        width: width ?? DEFAULT_WIDTH,
 | 
				
			||||||
 | 
					        height: height ?? DEFAULT_HEIGHT
 | 
				
			||||||
 | 
					    } as BrowserWindowConstructorOptions;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const storedDisplay = screen.getAllDisplays().find(display => display.id === State.store.displayid);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (x != null && y != null && storedDisplay) {
 | 
				
			||||||
 | 
					        options.x = x;
 | 
				
			||||||
 | 
					        options.y = y;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!Settings.store.disableMinSize) {
 | 
				
			||||||
 | 
					        options.minWidth = MIN_WIDTH;
 | 
				
			||||||
 | 
					        options.minHeight = MIN_HEIGHT;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return options;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function getDarwinOptions(): BrowserWindowConstructorOptions {
 | 
				
			||||||
 | 
					    const options = {
 | 
				
			||||||
 | 
					        titleBarStyle: "hidden",
 | 
				
			||||||
 | 
					        trafficLightPosition: { x: 10, y: 10 }
 | 
				
			||||||
 | 
					    } as BrowserWindowConstructorOptions;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const { splashTheming, splashBackground } = Settings.store;
 | 
				
			||||||
 | 
					    const { macosTranslucency } = VencordSettings.store;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (macosTranslucency) {
 | 
				
			||||||
 | 
					        options.vibrancy = "sidebar";
 | 
				
			||||||
 | 
					        options.backgroundColor = "#ffffff00";
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        if (splashTheming) {
 | 
				
			||||||
 | 
					            options.backgroundColor = splashBackground;
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            options.backgroundColor = nativeTheme.shouldUseDarkColors ? "#313338" : "#ffffff";
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return options;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function initWindowBoundsListeners(win: BrowserWindow) {
 | 
				
			||||||
 | 
					    const saveState = () => {
 | 
				
			||||||
 | 
					        State.store.maximized = win.isMaximized();
 | 
				
			||||||
 | 
					        State.store.minimized = win.isMinimized();
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    win.on("maximize", saveState);
 | 
				
			||||||
 | 
					    win.on("minimize", saveState);
 | 
				
			||||||
 | 
					    win.on("unmaximize", saveState);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const saveBounds = () => {
 | 
				
			||||||
 | 
					        State.store.windowBounds = win.getBounds();
 | 
				
			||||||
 | 
					        State.store.displayid = screen.getDisplayMatching(State.store.windowBounds).id;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    win.on("resize", saveBounds);
 | 
				
			||||||
 | 
					    win.on("move", saveBounds);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function initSettingsListeners(win: BrowserWindow) {
 | 
				
			||||||
 | 
					    addSettingsListener("tray", enable => {
 | 
				
			||||||
 | 
					        if (enable) initTray(win);
 | 
				
			||||||
 | 
					        else tray?.destroy();
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    addSettingsListener("disableMinSize", disable => {
 | 
				
			||||||
 | 
					        if (disable) {
 | 
				
			||||||
 | 
					            // 0 no work
 | 
				
			||||||
 | 
					            win.setMinimumSize(1, 1);
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            win.setMinimumSize(MIN_WIDTH, MIN_HEIGHT);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            const { width, height } = win.getBounds();
 | 
				
			||||||
 | 
					            win.setBounds({
 | 
				
			||||||
 | 
					                width: Math.max(width, MIN_WIDTH),
 | 
				
			||||||
 | 
					                height: Math.max(height, MIN_HEIGHT)
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    addVencordSettingsListener("macosTranslucency", enabled => {
 | 
				
			||||||
 | 
					        if (enabled) {
 | 
				
			||||||
 | 
					            win.setVibrancy("sidebar");
 | 
				
			||||||
 | 
					            win.setBackgroundColor("#ffffff00");
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            win.setVibrancy(null);
 | 
				
			||||||
 | 
					            win.setBackgroundColor("#ffffff");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    addSettingsListener("enableMenu", enabled => {
 | 
				
			||||||
 | 
					        win.setAutoHideMenuBar(enabled ?? false);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    addSettingsListener("spellCheckLanguages", languages => initSpellCheckLanguages(win, languages));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async function initSpellCheckLanguages(win: BrowserWindow, languages?: string[]) {
 | 
				
			||||||
 | 
					    languages ??= await win.webContents.executeJavaScript("[...new Set(navigator.languages)]").catch(() => []);
 | 
				
			||||||
 | 
					    if (!languages) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const ses = session.defaultSession;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const available = ses.availableSpellCheckerLanguages;
 | 
				
			||||||
 | 
					    const applicable = languages.filter(l => available.includes(l)).slice(0, 5);
 | 
				
			||||||
 | 
					    if (applicable.length) ses.setSpellCheckerLanguages(applicable);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function initSpellCheck(win: BrowserWindow) {
 | 
				
			||||||
 | 
					    win.webContents.on("context-menu", (_, data) => {
 | 
				
			||||||
 | 
					        win.webContents.send(IpcEvents.SPELLCHECK_RESULT, data.misspelledWord, data.dictionarySuggestions);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    initSpellCheckLanguages(win, Settings.store.spellCheckLanguages);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function createMainWindow() {
 | 
				
			||||||
 | 
					    // Clear up previous settings listeners
 | 
				
			||||||
 | 
					    removeSettingsListeners();
 | 
				
			||||||
 | 
					    removeVencordSettingsListeners();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const { staticTitle, transparencyOption, enableMenu, customTitleBar } = Settings.store;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const { frameless, transparent } = VencordSettings.store;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const noFrame = frameless === true || customTitleBar === true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const win = (mainWin = new BrowserWindow({
 | 
				
			||||||
 | 
					        show: false,
 | 
				
			||||||
 | 
					        webPreferences: {
 | 
				
			||||||
 | 
					            nodeIntegration: false,
 | 
				
			||||||
 | 
					            sandbox: false,
 | 
				
			||||||
 | 
					            contextIsolation: true,
 | 
				
			||||||
 | 
					            devTools: true,
 | 
				
			||||||
 | 
					            preload: join(__dirname, "preload.js"),
 | 
				
			||||||
 | 
					            spellcheck: true,
 | 
				
			||||||
 | 
					            // disable renderer backgrounding to prevent the app from unloading when in the background
 | 
				
			||||||
 | 
					            backgroundThrottling: false
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        icon: ICON_PATH,
 | 
				
			||||||
 | 
					        frame: !noFrame,
 | 
				
			||||||
 | 
					        ...(transparent && {
 | 
				
			||||||
 | 
					            transparent: true,
 | 
				
			||||||
 | 
					            backgroundColor: "#00000000"
 | 
				
			||||||
 | 
					        }),
 | 
				
			||||||
 | 
					        ...(transparencyOption &&
 | 
				
			||||||
 | 
					            transparencyOption !== "none" && {
 | 
				
			||||||
 | 
					                backgroundColor: "#00000000",
 | 
				
			||||||
 | 
					                backgroundMaterial: transparencyOption
 | 
				
			||||||
 | 
					            }),
 | 
				
			||||||
 | 
					        // Fix transparencyOption for custom discord titlebar
 | 
				
			||||||
 | 
					        ...(customTitleBar &&
 | 
				
			||||||
 | 
					            transparencyOption &&
 | 
				
			||||||
 | 
					            transparencyOption !== "none" && {
 | 
				
			||||||
 | 
					                transparent: true
 | 
				
			||||||
 | 
					            }),
 | 
				
			||||||
 | 
					        ...(staticTitle && { title: "Vesktop" }),
 | 
				
			||||||
 | 
					        ...(process.platform === "darwin" && getDarwinOptions()),
 | 
				
			||||||
 | 
					        ...getWindowBoundsOptions(),
 | 
				
			||||||
 | 
					        autoHideMenuBar: enableMenu
 | 
				
			||||||
 | 
					    }));
 | 
				
			||||||
 | 
					    win.setMenuBarVisibility(false);
 | 
				
			||||||
 | 
					    if (process.platform === "darwin" && customTitleBar) win.setWindowButtonVisibility(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    win.on("close", e => {
 | 
				
			||||||
 | 
					        const useTray = !isDeckGameMode && Settings.store.minimizeToTray !== false && Settings.store.tray !== false;
 | 
				
			||||||
 | 
					        if (isQuitting || (process.platform !== "darwin" && !useTray)) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        e.preventDefault();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (process.platform === "darwin") app.hide();
 | 
				
			||||||
 | 
					        else win.hide();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (Settings.store.staticTitle) win.on("page-title-updated", e => e.preventDefault());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    initWindowBoundsListeners(win);
 | 
				
			||||||
 | 
					    if (!isDeckGameMode && (Settings.store.tray ?? true) && process.platform !== "darwin") initTray(win);
 | 
				
			||||||
 | 
					    initMenuBar(win);
 | 
				
			||||||
 | 
					    makeLinksOpenExternally(win);
 | 
				
			||||||
 | 
					    initSettingsListeners(win);
 | 
				
			||||||
 | 
					    initSpellCheck(win);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    win.webContents.setUserAgent(BrowserUserAgent);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const subdomain =
 | 
				
			||||||
 | 
					        Settings.store.discordBranch === "canary" || Settings.store.discordBranch === "ptb"
 | 
				
			||||||
 | 
					            ? `${Settings.store.discordBranch}.`
 | 
				
			||||||
 | 
					            : "";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    win.loadURL(`https://${subdomain}discord.com/app`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return win;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					const fs = require('fs');
 | 
				
			||||||
 | 
					const path = require('path');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const isvencorddisabled: boolean = fs.existsSync(path.join(app.getPath('exe'), '..', 'be_gone_vendicated.txt'));
 | 
				
			||||||
 | 
					const runVencordMain = once(() => require(join(VENCORD_FILES_DIR, "vencordDesktopMain.js")));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const extension = path.join(app.getPath('exe'), '..', 'extension');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function vendicated() {
 | 
				
			||||||
 | 
					    if (!isvencorddisabled) {
 | 
				
			||||||
 | 
					        runVencordMain();
 | 
				
			||||||
 | 
					        console.log('vencord is enabled');
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        console.log('vencord is disabled');
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export async function createWindows() {
 | 
				
			||||||
 | 
					    const startMinimized = process.argv.includes("--start-minimized");
 | 
				
			||||||
 | 
					    const splash = createSplashWindow(startMinimized);
 | 
				
			||||||
 | 
					    // SteamOS letterboxes and scales it terribly, so just full screen it
 | 
				
			||||||
 | 
					    if (isDeckGameMode) splash.setFullScreen(true);
 | 
				
			||||||
 | 
					    await ensureVencordFiles();
 | 
				
			||||||
 | 
					    vendicated();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    mainWin = createMainWindow();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    session.defaultSession.loadExtension(extension)
 | 
				
			||||||
 | 
					    .then(() => {
 | 
				
			||||||
 | 
					        console.log('extension is loaded');
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    .catch(() => {
 | 
				
			||||||
 | 
					        console.warn('extension is not loaded');
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    .finally(() => {
 | 
				
			||||||
 | 
					        mainWin.webContents.on("did-finish-load", () => {
 | 
				
			||||||
 | 
					            splash.destroy();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (!startMinimized) {
 | 
				
			||||||
 | 
					                mainWin.show();
 | 
				
			||||||
 | 
					                if (State.store.maximized && !isDeckGameMode) mainWin.maximize();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (isDeckGameMode) {
 | 
				
			||||||
 | 
					                mainWin.setFullScreen(true);
 | 
				
			||||||
 | 
					                askToApplySteamLayout(mainWin);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            mainWin.once("show", () => {
 | 
				
			||||||
 | 
					                if (State.store.maximized && !mainWin.isMaximized() && !isDeckGameMode) {
 | 
				
			||||||
 | 
					                    mainWin.maximize();
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        initArRPC();
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										28
									
								
								src/main/mediaPermissions.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,28 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * SPDX-License-Identifier: GPL-3.0
 | 
				
			||||||
 | 
					 * Modified for Aerocord, originally part of Vesktop, a desktop app aiming to give you a snappier Discord Experience
 | 
				
			||||||
 | 
					 * Copyright (c) 2024-2024 Aiek 
 | 
				
			||||||
 | 
					 * Copyright (c) 2024 RandomServer Community
 | 
				
			||||||
 | 
					 * Copyright (c) 2023 Vendicated and Vencord contributors
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { session, systemPreferences } from "electron";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function registerMediaPermissionsHandler() {
 | 
				
			||||||
 | 
					    if (process.platform !== "darwin") return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    session.defaultSession.setPermissionRequestHandler(async (_webContents, permission, callback, details) => {
 | 
				
			||||||
 | 
					        let granted = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if ("mediaTypes" in details) {
 | 
				
			||||||
 | 
					            if (details.mediaTypes?.includes("audio")) {
 | 
				
			||||||
 | 
					                granted &&= await systemPreferences.askForMediaAccess("microphone");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            if (details.mediaTypes?.includes("video")) {
 | 
				
			||||||
 | 
					                granted &&= await systemPreferences.askForMediaAccess("camera");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        callback(granted);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										86
									
								
								src/main/screenShare.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,86 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * SPDX-License-Identifier: GPL-3.0
 | 
				
			||||||
 | 
					 * Modified for Aerocord, originally part of Vesktop, a desktop app aiming to give you a snappier Discord Experience
 | 
				
			||||||
 | 
					 * Copyright (c) 2024-2024 Aiek 
 | 
				
			||||||
 | 
					 * Copyright (c) 2024 RandomServer Community
 | 
				
			||||||
 | 
					 * Copyright (c) 2023 Vendicated and Vencord contributors
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { desktopCapturer, session, Streams } from "electron";
 | 
				
			||||||
 | 
					import type { StreamPick } from "renderer/components/ScreenSharePicker";
 | 
				
			||||||
 | 
					import { IpcEvents } from "shared/IpcEvents";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { handle } from "./utils/ipcWrappers";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const isWayland =
 | 
				
			||||||
 | 
					    process.platform === "linux" && (process.env.XDG_SESSION_TYPE === "wayland" || !!process.env.WAYLAND_DISPLAY);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function registerScreenShareHandler() {
 | 
				
			||||||
 | 
					    handle(IpcEvents.CAPTURER_GET_LARGE_THUMBNAIL, async (_, id: string) => {
 | 
				
			||||||
 | 
					        const sources = await desktopCapturer.getSources({
 | 
				
			||||||
 | 
					            types: ["window", "screen"],
 | 
				
			||||||
 | 
					            thumbnailSize: {
 | 
				
			||||||
 | 
					                width: 1920,
 | 
				
			||||||
 | 
					                height: 1080
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        return sources.find(s => s.id === id)?.thumbnail.toDataURL();
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    session.defaultSession.setDisplayMediaRequestHandler(async (request, callback) => {
 | 
				
			||||||
 | 
					        // request full resolution on wayland right away because we always only end up with one result anyway
 | 
				
			||||||
 | 
					        const width = isWayland ? 1920 : 176;
 | 
				
			||||||
 | 
					        const sources = await desktopCapturer
 | 
				
			||||||
 | 
					            .getSources({
 | 
				
			||||||
 | 
					                types: ["window", "screen"],
 | 
				
			||||||
 | 
					                thumbnailSize: {
 | 
				
			||||||
 | 
					                    width,
 | 
				
			||||||
 | 
					                    height: width * (9 / 16)
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					            .catch(err => console.error("Error during screenshare picker", err));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!sources) return callback({});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const data = sources.map(({ id, name, thumbnail }) => ({
 | 
				
			||||||
 | 
					            id,
 | 
				
			||||||
 | 
					            name,
 | 
				
			||||||
 | 
					            url: thumbnail.toDataURL()
 | 
				
			||||||
 | 
					        }));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (isWayland) {
 | 
				
			||||||
 | 
					            const video = data[0];
 | 
				
			||||||
 | 
					            if (video) {
 | 
				
			||||||
 | 
					                const stream = await request
 | 
				
			||||||
 | 
					                    .frame!.executeJavaScript(
 | 
				
			||||||
 | 
					                        `Vesktop.Components.ScreenShare.openScreenSharePicker(${JSON.stringify([video])},true)`
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                    .catch(() => null);
 | 
				
			||||||
 | 
					                if (stream === null) return callback({});
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            callback(video ? { video: sources[0] } : {});
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const choice = await request
 | 
				
			||||||
 | 
					            .frame!.executeJavaScript(`Vesktop.Components.ScreenShare.openScreenSharePicker(${JSON.stringify(data)})`)
 | 
				
			||||||
 | 
					            .then(e => e as StreamPick)
 | 
				
			||||||
 | 
					            .catch(e => {
 | 
				
			||||||
 | 
					                console.error("Error during screenshare picker", e);
 | 
				
			||||||
 | 
					                return null;
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!choice) return callback({});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const source = sources.find(s => s.id === choice.id);
 | 
				
			||||||
 | 
					        if (!source) return callback({});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const streams: Streams = {
 | 
				
			||||||
 | 
					            video: source
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        if (choice.audio && process.platform === "win32") streams.audio = "loopback";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        callback(streams);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										60
									
								
								src/main/settings.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,60 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * SPDX-License-Identifier: GPL-3.0
 | 
				
			||||||
 | 
					 * Modified for Aerocord, originally part of Vesktop, a desktop app aiming to give you a snappier Discord Experience
 | 
				
			||||||
 | 
					 * Copyright (c) 2024-2024 Aiek 
 | 
				
			||||||
 | 
					 * Copyright (c) 2024 RandomServer Community
 | 
				
			||||||
 | 
					 * Copyright (c) 2023 Vendicated and Vencord contributors
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
 | 
				
			||||||
 | 
					import { dirname, join } from "path";
 | 
				
			||||||
 | 
					import type { Settings as TSettings, State as TState } from "shared/settings";
 | 
				
			||||||
 | 
					import { SettingsStore } from "shared/utils/SettingsStore";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { DATA_DIR, VENCORD_SETTINGS_FILE } from "./constants";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const SETTINGS_FILE = join(DATA_DIR, "settings.json");
 | 
				
			||||||
 | 
					const STATE_FILE = join(DATA_DIR, "state.json");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function loadSettings<T extends object = any>(file: string, name: string) {
 | 
				
			||||||
 | 
					    let settings = {} as T;
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					        const content = readFileSync(file, "utf8");
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            settings = JSON.parse(content);
 | 
				
			||||||
 | 
					        } catch (err) {
 | 
				
			||||||
 | 
					            console.error(`Failed to parse ${name}.json:`, err);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    } catch {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const store = new SettingsStore(settings);
 | 
				
			||||||
 | 
					    store.addGlobalChangeListener(o => {
 | 
				
			||||||
 | 
					        mkdirSync(dirname(file), { recursive: true });
 | 
				
			||||||
 | 
					        writeFileSync(file, JSON.stringify(o, null, 4));
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return store;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const Settings = loadSettings<TSettings>(SETTINGS_FILE, "Vesktop settings");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const VencordSettings = loadSettings<any>(VENCORD_SETTINGS_FILE, "Vencord settings");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if (Object.hasOwn(Settings.plain, "firstLaunch") && !existsSync(STATE_FILE)) {
 | 
				
			||||||
 | 
					    console.warn("legacy state in settings.json detected. migrating to state.json");
 | 
				
			||||||
 | 
					    const state = {} as TState;
 | 
				
			||||||
 | 
					    for (const prop of [
 | 
				
			||||||
 | 
					        "firstLaunch",
 | 
				
			||||||
 | 
					        "maximized",
 | 
				
			||||||
 | 
					        "minimized",
 | 
				
			||||||
 | 
					        "skippedUpdate",
 | 
				
			||||||
 | 
					        "steamOSLayoutVersion",
 | 
				
			||||||
 | 
					        "windowBounds"
 | 
				
			||||||
 | 
					    ] as const) {        state[prop] = Settings.plain[prop];
 | 
				
			||||||
 | 
					        delete Settings.plain[prop];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    Settings.markAsChanged();
 | 
				
			||||||
 | 
					    writeFileSync(STATE_FILE, JSON.stringify(state, null, 4));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const State = loadSettings<TState>(STATE_FILE, "Vesktop state");
 | 
				
			||||||
							
								
								
									
										41
									
								
								src/main/splash.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,41 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * SPDX-License-Identifier: GPL-3.0
 | 
				
			||||||
 | 
					 * Modified for Aerocord, originally part of Vesktop, a desktop app aiming to give you a snappier Discord Experience
 | 
				
			||||||
 | 
					 * Copyright (c) 2024-2024 Aiek 
 | 
				
			||||||
 | 
					 * Copyright (c) 2024 RandomServer Community
 | 
				
			||||||
 | 
					 * Copyright (c) 2023 Vendicated and Vencord contributors
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { BrowserWindow } from "electron";
 | 
				
			||||||
 | 
					import { join } from "path";
 | 
				
			||||||
 | 
					import { SplashProps } from "shared/browserWinProperties";
 | 
				
			||||||
 | 
					import { ICON_PATH, VIEW_DIR } from "shared/paths";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { Settings } from "./settings";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function createSplashWindow(startMinimized = false) {
 | 
				
			||||||
 | 
					    const splash = new BrowserWindow({
 | 
				
			||||||
 | 
					        ...SplashProps,
 | 
				
			||||||
 | 
					        icon: ICON_PATH,
 | 
				
			||||||
 | 
					        show: !startMinimized
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    splash.loadFile(join(VIEW_DIR, "splash.html"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const { splashBackground, splashColor, splashTheming } = Settings.store;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (splashTheming) {
 | 
				
			||||||
 | 
					        if (splashColor) {
 | 
				
			||||||
 | 
					            const semiTransparentSplashColor = splashColor.replace("rgb(", "rgba(").replace(")", ", 0.2)");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            splash.webContents.insertCSS(`body { --fg: ${splashColor} !important }`);
 | 
				
			||||||
 | 
					            splash.webContents.insertCSS(`body { --fg-semi-trans: ${semiTransparentSplashColor} !important }`);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (splashBackground) {
 | 
				
			||||||
 | 
					            splash.webContents.insertCSS(`body { --bg: ${splashBackground} !important }`);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return splash;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										60
									
								
								src/main/utils/http.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,60 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * SPDX-License-Identifier: GPL-3.0
 | 
				
			||||||
 | 
					 * Modified for Aerocord, originally part of Vesktop, a desktop app aiming to give you a snappier Discord Experience
 | 
				
			||||||
 | 
					 * Copyright (c) 2024-2024 Aiek 
 | 
				
			||||||
 | 
					 * Copyright (c) 2024 RandomServer Community
 | 
				
			||||||
 | 
					 * Copyright (c) 2023 Vendicated and Vencord contributors
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { createWriteStream } from "fs";
 | 
				
			||||||
 | 
					import { Readable } from "stream";
 | 
				
			||||||
 | 
					import { pipeline } from "stream/promises";
 | 
				
			||||||
 | 
					import { setTimeout } from "timers/promises";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface FetchieOptions {
 | 
				
			||||||
 | 
					    retryOnNetworkError?: boolean;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export async function downloadFile(url: string, file: string, options: RequestInit = {}, fetchieOpts?: FetchieOptions) {
 | 
				
			||||||
 | 
					    const res = await fetchie(url, options, fetchieOpts);
 | 
				
			||||||
 | 
					    await pipeline(
 | 
				
			||||||
 | 
					        // @ts-expect-error odd type error
 | 
				
			||||||
 | 
					        Readable.fromWeb(res.body!),
 | 
				
			||||||
 | 
					        createWriteStream(file, {
 | 
				
			||||||
 | 
					            autoClose: true
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const ONE_MINUTE_MS = 1000 * 60;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export async function fetchie(url: string, options?: RequestInit, { retryOnNetworkError }: FetchieOptions = {}) {
 | 
				
			||||||
 | 
					    let res: Response | undefined;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					        res = await fetch(url, options);
 | 
				
			||||||
 | 
					    } catch (err) {
 | 
				
			||||||
 | 
					        if (retryOnNetworkError) {
 | 
				
			||||||
 | 
					            console.error("Failed to fetch", url + ".", "Gonna retry with backoff.");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            for (let tries = 0, delayMs = 500; tries < 20; tries++, delayMs = Math.min(2 * delayMs, ONE_MINUTE_MS)) {
 | 
				
			||||||
 | 
					                await setTimeout(delayMs);
 | 
				
			||||||
 | 
					                try {
 | 
				
			||||||
 | 
					                    res = await fetch(url, options);
 | 
				
			||||||
 | 
					                    break;
 | 
				
			||||||
 | 
					                } catch {}
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!res) throw new Error(`Failed to fetch ${url}\n${err}`);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (res.ok) return res;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let msg = `Got non-OK response for ${url}: ${res.status} ${res.statusText}`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const reason = await res.text().catch(() => "");
 | 
				
			||||||
 | 
					    if (reason) msg += `\n${reason}`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    throw new Error(msg);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										34
									
								
								src/main/utils/ipcWrappers.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,34 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * SPDX-License-Identifier: GPL-3.0
 | 
				
			||||||
 | 
					 * Modified for Aerocord, originally part of Vesktop, a desktop app aiming to give you a snappier Discord Experience
 | 
				
			||||||
 | 
					 * Copyright (c) 2024-2024 Aiek 
 | 
				
			||||||
 | 
					 * Copyright (c) 2024 RandomServer Community
 | 
				
			||||||
 | 
					 * Copyright (c) 2023 Vendicated and Vencord contributors
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { ipcMain, IpcMainEvent, IpcMainInvokeEvent, WebFrameMain } from "electron";
 | 
				
			||||||
 | 
					import { DISCORD_HOSTNAMES } from "main/constants";
 | 
				
			||||||
 | 
					import { IpcEvents } from "shared/IpcEvents";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function validateSender(frame: WebFrameMain | null) {
 | 
				
			||||||
 | 
					    if (!frame) throw new Error("ipc: No sender frame");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const { hostname, protocol } = new URL(frame.url);
 | 
				
			||||||
 | 
					    if (protocol === "file:") return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!DISCORD_HOSTNAMES.includes(hostname)) throw new Error("ipc: Disallowed host " + hostname);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function handleSync(event: IpcEvents, cb: (e: IpcMainEvent, ...args: any[]) => any) {
 | 
				
			||||||
 | 
					    ipcMain.on(event, (e, ...args) => {
 | 
				
			||||||
 | 
					        validateSender(e.senderFrame);
 | 
				
			||||||
 | 
					        e.returnValue = cb(e, ...args);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function handle(event: IpcEvents, cb: (e: IpcMainInvokeEvent, ...args: any[]) => any) {
 | 
				
			||||||
 | 
					    ipcMain.handle(event, (e, ...args) => {
 | 
				
			||||||
 | 
					        validateSender(e.senderFrame);
 | 
				
			||||||
 | 
					        return cb(e, ...args);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										73
									
								
								src/main/utils/makeLinksOpenExternally.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,73 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * SPDX-License-Identifier: GPL-3.0
 | 
				
			||||||
 | 
					 * Modified for Aerocord, originally part of Vesktop, a desktop app aiming to give you a snappier Discord Experience
 | 
				
			||||||
 | 
					 * Copyright (c) 2024-2024 Aiek 
 | 
				
			||||||
 | 
					 * Copyright (c) 2024 RandomServer Community
 | 
				
			||||||
 | 
					 * Copyright (c) 2023 Vendicated and Vencord contributors
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { BrowserWindow, shell } from "electron";
 | 
				
			||||||
 | 
					import { DISCORD_HOSTNAMES } from "main/constants";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { Settings } from "../settings";
 | 
				
			||||||
 | 
					import { createOrFocusPopup, setupPopout } from "./popout";
 | 
				
			||||||
 | 
					import { execSteamURL, isDeckGameMode, steamOpenURL } from "./steamOS";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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) {
 | 
				
			||||||
 | 
					    win.webContents.setWindowOpenHandler(({ url, frameName, features }) => {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            var { protocol, hostname, pathname } = new URL(url);
 | 
				
			||||||
 | 
					        } catch {
 | 
				
			||||||
 | 
					            return { action: "deny" };
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (frameName.startsWith("DISCORD_") && pathname === "/popout" && DISCORD_HOSTNAMES.includes(hostname)) {
 | 
				
			||||||
 | 
					            return createOrFocusPopup(frameName, features);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        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);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										118
									
								
								src/main/utils/popout.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,118 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * SPDX-License-Identifier: GPL-3.0
 | 
				
			||||||
 | 
					 * Modified for Aerocord, originally part of Vesktop, a desktop app aiming to give you a snappier Discord Experience
 | 
				
			||||||
 | 
					 * Copyright (c) 2024-2024 Aiek 
 | 
				
			||||||
 | 
					 * Copyright (c) 2024 RandomServer Community
 | 
				
			||||||
 | 
					 * Copyright (c) 2023 Vendicated and Vencord contributors
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { BrowserWindow, BrowserWindowConstructorOptions } from "electron";
 | 
				
			||||||
 | 
					import { Settings } from "main/settings";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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: Settings.store.customTitleBar !== true,
 | 
				
			||||||
 | 
					    titleBarStyle: process.platform === "darwin" ? "hidden" : undefined,
 | 
				
			||||||
 | 
					    trafficLightPosition:
 | 
				
			||||||
 | 
					        process.platform === "darwin"
 | 
				
			||||||
 | 
					            ? {
 | 
				
			||||||
 | 
					                  x: 10,
 | 
				
			||||||
 | 
					                  y: 3
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					            : undefined,
 | 
				
			||||||
 | 
					    webPreferences: {
 | 
				
			||||||
 | 
					        nodeIntegration: false,
 | 
				
			||||||
 | 
					        contextIsolation: true
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    autoHideMenuBar: Settings.store.enableMenu
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export 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) {
 | 
				
			||||||
 | 
					    win.setMenuBarVisibility(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    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);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										99
									
								
								src/main/utils/steamOS.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,99 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * SPDX-License-Identifier: GPL-3.0
 | 
				
			||||||
 | 
					 * Modified for Aerocord, originally part of Vesktop, a desktop app aiming to give you a snappier Discord Experience
 | 
				
			||||||
 | 
					 * Copyright (c) 2024-2024 Aiek 
 | 
				
			||||||
 | 
					 * Copyright (c) 2024 RandomServer Community
 | 
				
			||||||
 | 
					 * Copyright (c) 2023 Vendicated and Vencord contributors
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { BrowserWindow, dialog } from "electron";
 | 
				
			||||||
 | 
					import { writeFile } from "fs/promises";
 | 
				
			||||||
 | 
					import { join } from "path";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { MessageBoxChoice } from "../constants";
 | 
				
			||||||
 | 
					import { State } from "../settings";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Bump this to re-show the prompt
 | 
				
			||||||
 | 
					const layoutVersion = 2;
 | 
				
			||||||
 | 
					// Get this from "show details" on the profile after exporting as a shared personal layout or using share with community
 | 
				
			||||||
 | 
					const layoutId = "3080264545"; // Vesktop Layout v2
 | 
				
			||||||
 | 
					const numberRegex = /^[0-9]*$/;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let steamPipeQueue = Promise.resolve();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const isDeckGameMode = process.env.SteamOS === "1" && process.env.SteamGamepadUI === "1";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function applyDeckKeyboardFix() {
 | 
				
			||||||
 | 
					    if (!isDeckGameMode) return;
 | 
				
			||||||
 | 
					    // Prevent constant virtual keyboard spam that eventually crashes Steam.
 | 
				
			||||||
 | 
					    process.env.GTK_IM_MODULE = "None";
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// For some reason SteamAppId is always 0 for non-steam apps so we do this insanity instead.
 | 
				
			||||||
 | 
					function getAppId(): string | null {
 | 
				
			||||||
 | 
					    // /home/deck/.local/share/Steam/steamapps/shadercache/APPID/fozmediav1
 | 
				
			||||||
 | 
					    const path = process.env.STEAM_COMPAT_MEDIA_PATH;
 | 
				
			||||||
 | 
					    if (!path) return null;
 | 
				
			||||||
 | 
					    const pathElems = path?.split("/");
 | 
				
			||||||
 | 
					    const appId = pathElems[pathElems.length - 2];
 | 
				
			||||||
 | 
					    if (appId.match(numberRegex)) {
 | 
				
			||||||
 | 
					        console.log(`Got Steam App ID ${appId}`);
 | 
				
			||||||
 | 
					        return appId;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return null;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function execSteamURL(url: string) {
 | 
				
			||||||
 | 
					    // This doesn't allow arbitrary execution despite the weird syntax.
 | 
				
			||||||
 | 
					    steamPipeQueue = steamPipeQueue.then(() =>
 | 
				
			||||||
 | 
					        writeFile(
 | 
				
			||||||
 | 
					            join(process.env.HOME || "/home/deck", ".steam", "steam.pipe"),
 | 
				
			||||||
 | 
					            // replace ' to prevent argument injection
 | 
				
			||||||
 | 
					            `'${process.env.HOME}/.local/share/Steam/ubuntu12_32/steam' '-ifrunning' '${url.replaceAll("'", "%27")}'\n`,
 | 
				
			||||||
 | 
					            "utf-8"
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function steamOpenURL(url: string) {
 | 
				
			||||||
 | 
					    execSteamURL(`steam://openurl/${url}`);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export async function showGamePage() {
 | 
				
			||||||
 | 
					    const appId = getAppId();
 | 
				
			||||||
 | 
					    if (!appId) return;
 | 
				
			||||||
 | 
					    await execSteamURL(`steam://nav/games/details/${appId}`);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async function showLayout(appId: string) {
 | 
				
			||||||
 | 
					    execSteamURL(`steam://controllerconfig/${appId}/${layoutId}`);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export async function askToApplySteamLayout(win: BrowserWindow) {
 | 
				
			||||||
 | 
					    const appId = getAppId();
 | 
				
			||||||
 | 
					    if (!appId) return;
 | 
				
			||||||
 | 
					    if (State.store.steamOSLayoutVersion === layoutVersion) return;
 | 
				
			||||||
 | 
					    const update = Boolean(State.store.steamOSLayoutVersion);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Touch screen breaks in some menus when native touch mode is enabled on latest SteamOS beta, remove most of the update specific text once that's fixed.
 | 
				
			||||||
 | 
					    const { response } = await dialog.showMessageBox(win, {
 | 
				
			||||||
 | 
					        message: `${update ? "Update" : "Apply"} Vesktop Steam Input Layout?`,
 | 
				
			||||||
 | 
					        detail: `Would you like to ${update ? "Update" : "Apply"} Vesktop's recommended Steam Deck controller settings?
 | 
				
			||||||
 | 
					${update ? "Click yes using the touchpad" : "Tap yes"}, then press the X button or tap Apply Layout to confirm.${
 | 
				
			||||||
 | 
					            update ? " Doing so will undo any customizations you have made." : ""
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					${update ? "Click" : "Tap"} no to keep your current layout.`,
 | 
				
			||||||
 | 
					        buttons: ["Yes", "No"],
 | 
				
			||||||
 | 
					        cancelId: MessageBoxChoice.Cancel,
 | 
				
			||||||
 | 
					        defaultId: MessageBoxChoice.Default,
 | 
				
			||||||
 | 
					        type: "question"
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (State.store.steamOSLayoutVersion !== layoutVersion) {
 | 
				
			||||||
 | 
					        State.store.steamOSLayoutVersion = layoutVersion;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (response === MessageBoxChoice.Cancel) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await showLayout(appId);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										77
									
								
								src/main/utils/vencordLoader.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,77 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * SPDX-License-Identifier: GPL-3.0
 | 
				
			||||||
 | 
					 * Modified for Aerocord, originally part of Vesktop, a desktop app aiming to give you a snappier Discord Experience
 | 
				
			||||||
 | 
					 * Copyright (c) 2024-2024 Aiek 
 | 
				
			||||||
 | 
					 * Copyright (c) 2024 RandomServer Community
 | 
				
			||||||
 | 
					 * Copyright (c) 2023 Vendicated and Vencord contributors
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { mkdirSync } from "fs";
 | 
				
			||||||
 | 
					import { access, constants as FsConstants } from "fs/promises";
 | 
				
			||||||
 | 
					import { join } from "path";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { USER_AGENT, VENCORD_FILES_DIR } from "../constants";
 | 
				
			||||||
 | 
					import { downloadFile, fetchie } from "./http";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const API_BASE = "https://api.github.com";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const FILES_TO_DOWNLOAD = [
 | 
				
			||||||
 | 
					    "vencordDesktopMain.js",
 | 
				
			||||||
 | 
					    "vencordDesktopPreload.js",
 | 
				
			||||||
 | 
					    "vencordDesktopRenderer.js",
 | 
				
			||||||
 | 
					    "vencordDesktopRenderer.css"
 | 
				
			||||||
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface ReleaseData {
 | 
				
			||||||
 | 
					    name: string;
 | 
				
			||||||
 | 
					    tag_name: string;
 | 
				
			||||||
 | 
					    html_url: string;
 | 
				
			||||||
 | 
					    assets: Array<{
 | 
				
			||||||
 | 
					        name: string;
 | 
				
			||||||
 | 
					        browser_download_url: string;
 | 
				
			||||||
 | 
					    }>;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export async function githubGet(endpoint: string) {
 | 
				
			||||||
 | 
					    const opts: RequestInit = {
 | 
				
			||||||
 | 
					        headers: {
 | 
				
			||||||
 | 
					            Accept: "application/vnd.github+json",
 | 
				
			||||||
 | 
					            "User-Agent": USER_AGENT
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (process.env.GITHUB_TOKEN) (opts.headers! as any).Authorization = `Bearer ${process.env.GITHUB_TOKEN}`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return fetchie(API_BASE + endpoint, opts, { retryOnNetworkError: true });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export async function downloadVencordFiles() {
 | 
				
			||||||
 | 
					    const release = await githubGet("/repos/Vendicated/Vencord/releases/latest");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const { assets }: ReleaseData = await release.json();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    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), {}, { retryOnNetworkError: true })
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const existsAsync = (path: string) =>
 | 
				
			||||||
 | 
					    access(path, FsConstants.F_OK)
 | 
				
			||||||
 | 
					        .then(() => true)
 | 
				
			||||||
 | 
					        .catch(() => false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export async function isValidVencordInstall(dir: string) {
 | 
				
			||||||
 | 
					    return Promise.all(FILES_TO_DOWNLOAD.map(f => existsAsync(join(dir, f)))).then(arr => !arr.includes(false));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export async function ensureVencordFiles() {
 | 
				
			||||||
 | 
					    if (await isValidVencordInstall(VENCORD_FILES_DIR)) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    mkdirSync(VENCORD_FILES_DIR, { recursive: true });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await downloadVencordFiles();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										137
									
								
								src/main/venmic.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,137 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * SPDX-License-Identifier: GPL-3.0
 | 
				
			||||||
 | 
					 * Modified for Aerocord, originally part of Vesktop, a desktop app aiming to give you a snappier Discord Experience
 | 
				
			||||||
 | 
					 * Copyright (c) 2024-2024 Aiek 
 | 
				
			||||||
 | 
					 * Copyright (c) 2024 RandomServer Community
 | 
				
			||||||
 | 
					 * Copyright (c) 2023 Vendicated and Vencord contributors
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import type { LinkData, Node, PatchBay as PatchBayType } from "@vencord/venmic";
 | 
				
			||||||
 | 
					import { app, ipcMain } from "electron";
 | 
				
			||||||
 | 
					import { join } from "path";
 | 
				
			||||||
 | 
					import { IpcEvents } from "shared/IpcEvents";
 | 
				
			||||||
 | 
					import { STATIC_DIR } from "shared/paths";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { Settings } from "./settings";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let PatchBay: typeof PatchBayType | undefined;
 | 
				
			||||||
 | 
					let patchBayInstance: PatchBayType | undefined;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let imported = false;
 | 
				
			||||||
 | 
					let initialized = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let hasPipewirePulse = false;
 | 
				
			||||||
 | 
					let isGlibCxxOutdated = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function importVenmic() {
 | 
				
			||||||
 | 
					    if (imported) {
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    imported = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					        PatchBay = (require(join(STATIC_DIR, `dist/venmic-${process.arch}.node`)) as typeof import("@vencord/venmic"))
 | 
				
			||||||
 | 
					            .PatchBay;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        hasPipewirePulse = PatchBay.hasPipeWire();
 | 
				
			||||||
 | 
					    } catch (e: any) {
 | 
				
			||||||
 | 
					        console.error("Failed to import venmic", e);
 | 
				
			||||||
 | 
					        isGlibCxxOutdated = (e?.stack || e?.message || "").toLowerCase().includes("glibc");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function obtainVenmic() {
 | 
				
			||||||
 | 
					    if (!imported) {
 | 
				
			||||||
 | 
					        importVenmic();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (PatchBay && !initialized) {
 | 
				
			||||||
 | 
					        initialized = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            patchBayInstance = new PatchBay();
 | 
				
			||||||
 | 
					        } catch (e: any) {
 | 
				
			||||||
 | 
					            console.error("Failed to instantiate venmic", e);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return patchBayInstance;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function getRendererAudioServicePid() {
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					        app
 | 
				
			||||||
 | 
					            .getAppMetrics()
 | 
				
			||||||
 | 
					            .find(proc => proc.name === "Audio Service")
 | 
				
			||||||
 | 
					            ?.pid?.toString() ?? "owo"
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ipcMain.handle(IpcEvents.VIRT_MIC_LIST, () => {
 | 
				
			||||||
 | 
					    const audioPid = getRendererAudioServicePid();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const { granularSelect } = Settings.store.audio ?? {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const targets = obtainVenmic()
 | 
				
			||||||
 | 
					        ?.list(granularSelect ? ["node.name"] : undefined)
 | 
				
			||||||
 | 
					        .filter(s => s["application.process.id"] !== audioPid);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return targets ? { ok: true, targets, hasPipewirePulse } : { ok: false, isGlibCxxOutdated };
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ipcMain.handle(IpcEvents.VIRT_MIC_START, (_, include: Node[]) => {
 | 
				
			||||||
 | 
					    const pid = getRendererAudioServicePid();
 | 
				
			||||||
 | 
					    const { ignoreDevices, ignoreInputMedia, ignoreVirtual, workaround } = Settings.store.audio ?? {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const data: LinkData = {
 | 
				
			||||||
 | 
					        include,
 | 
				
			||||||
 | 
					        exclude: [{ "application.process.id": pid }],
 | 
				
			||||||
 | 
					        ignore_devices: ignoreDevices
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (ignoreInputMedia ?? true) {
 | 
				
			||||||
 | 
					        data.exclude.push({ "media.class": "Stream/Input/Audio" });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (ignoreVirtual) {
 | 
				
			||||||
 | 
					        data.exclude.push({ "node.virtual": "true" });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (workaround) {
 | 
				
			||||||
 | 
					        data.workaround = [{ "application.process.id": pid, "media.name": "RecordStream" }];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return obtainVenmic()?.link(data);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ipcMain.handle(IpcEvents.VIRT_MIC_START_SYSTEM, (_, exclude: Node[]) => {
 | 
				
			||||||
 | 
					    const pid = getRendererAudioServicePid();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const { workaround, ignoreDevices, ignoreInputMedia, ignoreVirtual, onlySpeakers, onlyDefaultSpeakers } =
 | 
				
			||||||
 | 
					        Settings.store.audio ?? {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const data: LinkData = {
 | 
				
			||||||
 | 
					        include: [],
 | 
				
			||||||
 | 
					        exclude: [{ "application.process.id": pid }, ...exclude],
 | 
				
			||||||
 | 
					        only_speakers: onlySpeakers,
 | 
				
			||||||
 | 
					        ignore_devices: ignoreDevices,
 | 
				
			||||||
 | 
					        only_default_speakers: onlyDefaultSpeakers
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (ignoreInputMedia ?? true) {
 | 
				
			||||||
 | 
					        data.exclude.push({ "media.class": "Stream/Input/Audio" });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (ignoreVirtual) {
 | 
				
			||||||
 | 
					        data.exclude.push({ "node.virtual": "true" });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (workaround) {
 | 
				
			||||||
 | 
					        data.workaround = [{ "application.process.id": pid, "media.name": "RecordStream" }];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return obtainVenmic()?.link(data);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ipcMain.handle(IpcEvents.VIRT_MIC_STOP, () => obtainVenmic()?.unlink());
 | 
				
			||||||
							
								
								
									
										84
									
								
								src/preload/VesktopNative.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,84 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * SPDX-License-Identifier: GPL-3.0
 | 
				
			||||||
 | 
					 * Modified for Aerocord, originally part of Vesktop, a desktop app aiming to give you a snappier Discord Experience
 | 
				
			||||||
 | 
					 * Copyright (c) 2024-2024 Aiek 
 | 
				
			||||||
 | 
					 * Copyright (c) 2024 RandomServer Community
 | 
				
			||||||
 | 
					 * Copyright (c) 2023 Vendicated and Vencord contributors
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { Node } from "@vencord/venmic";
 | 
				
			||||||
 | 
					import { ipcRenderer } from "electron";
 | 
				
			||||||
 | 
					import type { Settings } from "shared/settings";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { IpcEvents } from "../shared/IpcEvents";
 | 
				
			||||||
 | 
					import { invoke, sendSync } from "./typedIpc";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type SpellCheckerResultCallback = (word: string, suggestions: string[]) => void;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const spellCheckCallbacks = new Set<SpellCheckerResultCallback>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ipcRenderer.on(IpcEvents.SPELLCHECK_RESULT, (_, w: string, s: string[]) => {
 | 
				
			||||||
 | 
					    spellCheckCallbacks.forEach(cb => cb(w, s));
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const VesktopNative = {
 | 
				
			||||||
 | 
					    app: {
 | 
				
			||||||
 | 
					        relaunch: () => invoke<void>(IpcEvents.RELAUNCH),
 | 
				
			||||||
 | 
					        getVersion: () => sendSync<void>(IpcEvents.GET_VERSION),
 | 
				
			||||||
 | 
					        setBadgeCount: (count: number) => invoke<void>(IpcEvents.SET_BADGE_COUNT, count),
 | 
				
			||||||
 | 
					        supportsWindowsTransparency: () => sendSync<boolean>(IpcEvents.SUPPORTS_WINDOWS_TRANSPARENCY)
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    autostart: {
 | 
				
			||||||
 | 
					        isEnabled: () => sendSync<boolean>(IpcEvents.AUTOSTART_ENABLED),
 | 
				
			||||||
 | 
					        enable: () => invoke<void>(IpcEvents.ENABLE_AUTOSTART),
 | 
				
			||||||
 | 
					        disable: () => invoke<void>(IpcEvents.DISABLE_AUTOSTART)
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    fileManager: {
 | 
				
			||||||
 | 
					        showItemInFolder: (path: string) => invoke<void>(IpcEvents.SHOW_ITEM_IN_FOLDER, path),
 | 
				
			||||||
 | 
					        getVencordDir: () => sendSync<string | undefined>(IpcEvents.GET_VENCORD_DIR),
 | 
				
			||||||
 | 
					        selectVencordDir: (value?: null) => invoke<"cancelled" | "invalid" | "ok">(IpcEvents.SELECT_VENCORD_DIR, value)
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    settings: {
 | 
				
			||||||
 | 
					        get: () => sendSync<Settings>(IpcEvents.GET_SETTINGS),
 | 
				
			||||||
 | 
					        set: (settings: Settings, path?: string) => invoke<void>(IpcEvents.SET_SETTINGS, settings, path)
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    spellcheck: {
 | 
				
			||||||
 | 
					        getAvailableLanguages: () => sendSync<string[]>(IpcEvents.SPELLCHECK_GET_AVAILABLE_LANGUAGES),
 | 
				
			||||||
 | 
					        onSpellcheckResult(cb: SpellCheckerResultCallback) {
 | 
				
			||||||
 | 
					            spellCheckCallbacks.add(cb);
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        offSpellcheckResult(cb: SpellCheckerResultCallback) {
 | 
				
			||||||
 | 
					            spellCheckCallbacks.delete(cb);
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        replaceMisspelling: (word: string) => invoke<void>(IpcEvents.SPELLCHECK_REPLACE_MISSPELLING, word),
 | 
				
			||||||
 | 
					        addToDictionary: (word: string) => invoke<void>(IpcEvents.SPELLCHECK_ADD_TO_DICTIONARY, word)
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    win: {
 | 
				
			||||||
 | 
					        focus: () => invoke<void>(IpcEvents.FOCUS),
 | 
				
			||||||
 | 
					        close: (key?: string) => invoke<void>(IpcEvents.CLOSE, key),
 | 
				
			||||||
 | 
					        minimize: () => invoke<void>(IpcEvents.MINIMIZE),
 | 
				
			||||||
 | 
					        maximize: () => invoke<void>(IpcEvents.MAXIMIZE)
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    capturer: {
 | 
				
			||||||
 | 
					        getLargeThumbnail: (id: string) => invoke<string>(IpcEvents.CAPTURER_GET_LARGE_THUMBNAIL, id)
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    /** only available on Linux. */
 | 
				
			||||||
 | 
					    virtmic: {
 | 
				
			||||||
 | 
					        list: () =>
 | 
				
			||||||
 | 
					            invoke<
 | 
				
			||||||
 | 
					                { ok: false; isGlibCxxOutdated: boolean } | { ok: true; targets: Node[]; hasPipewirePulse: boolean }
 | 
				
			||||||
 | 
					            >(IpcEvents.VIRT_MIC_LIST),
 | 
				
			||||||
 | 
					        start: (include: Node[]) => invoke<void>(IpcEvents.VIRT_MIC_START, include),
 | 
				
			||||||
 | 
					        startSystem: (exclude: Node[]) => invoke<void>(IpcEvents.VIRT_MIC_START_SYSTEM, exclude),
 | 
				
			||||||
 | 
					        stop: () => invoke<void>(IpcEvents.VIRT_MIC_STOP)
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    arrpc: {
 | 
				
			||||||
 | 
					        onActivity(cb: (data: string) => void) {
 | 
				
			||||||
 | 
					            ipcRenderer.on(IpcEvents.ARRPC_ACTIVITY, (_, data: string) => cb(data));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    clipboard: {
 | 
				
			||||||
 | 
					        copyImage: (imageBuffer: Uint8Array, imageSrc: string) =>
 | 
				
			||||||
 | 
					            invoke<void>(IpcEvents.CLIPBOARD_COPY_IMAGE, imageBuffer, imageSrc)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										47
									
								
								src/preload/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,47 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * SPDX-License-Identifier: GPL-3.0
 | 
				
			||||||
 | 
					 * Modified for Aerocord, originally part of Vesktop, a desktop app aiming to give you a snappier Discord Experience
 | 
				
			||||||
 | 
					 * Copyright (c) 2024-2024 Aiek 
 | 
				
			||||||
 | 
					 * Copyright (c) 2024 RandomServer Community
 | 
				
			||||||
 | 
					 * Copyright (c) 2023 Vendicated and Vencord contributors
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { contextBridge, ipcRenderer, webFrame } from "electron";
 | 
				
			||||||
 | 
					import { readFileSync, watch } from "fs";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { IpcEvents } from "../shared/IpcEvents";
 | 
				
			||||||
 | 
					import { VesktopNative } from "./VesktopNative";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    contextBridge.exposeInMainWorld("VesktopNative", VesktopNative);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    require(ipcRenderer.sendSync(IpcEvents.GET_VENCORD_PRELOAD_FILE));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					webFrame.executeJavaScript(ipcRenderer.sendSync(IpcEvents.GET_VENCORD_RENDERER_SCRIPT));
 | 
				
			||||||
 | 
					webFrame.executeJavaScript(ipcRenderer.sendSync(IpcEvents.GET_RENDERER_SCRIPT));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// #region css
 | 
				
			||||||
 | 
					const rendererCss = ipcRenderer.sendSync(IpcEvents.GET_RENDERER_CSS_FILE);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const style = document.createElement("style");
 | 
				
			||||||
 | 
					style.id = "vcd-css-core";
 | 
				
			||||||
 | 
					style.textContent = readFileSync(rendererCss, "utf-8");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if (document.readyState === "complete") {
 | 
				
			||||||
 | 
					    document.documentElement.appendChild(style);
 | 
				
			||||||
 | 
					} else {
 | 
				
			||||||
 | 
					    document.addEventListener("DOMContentLoaded", () => document.documentElement.appendChild(style), {
 | 
				
			||||||
 | 
					        once: true
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if (IS_DEV) {
 | 
				
			||||||
 | 
					    // persistent means keep process running if watcher is the only thing still running
 | 
				
			||||||
 | 
					    // which we obviously don't want
 | 
				
			||||||
 | 
					    watch(rendererCss, { persistent: false }, () => {
 | 
				
			||||||
 | 
					        document.getElementById("vcd-css-core")!.textContent = readFileSync(rendererCss, "utf-8");
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					// #endregion
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										18
									
								
								src/preload/typedIpc.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,18 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * SPDX-License-Identifier: GPL-3.0
 | 
				
			||||||
 | 
					 * Modified for Aerocord, originally part of Vesktop, a desktop app aiming to give you a snappier Discord Experience
 | 
				
			||||||
 | 
					 * Copyright (c) 2024-2024 Aiek 
 | 
				
			||||||
 | 
					 * Copyright (c) 2024 RandomServer Community
 | 
				
			||||||
 | 
					 * Copyright (c) 2023 Vendicated and Vencord contributors
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { ipcRenderer } from "electron";
 | 
				
			||||||
 | 
					import { IpcEvents } from "shared/IpcEvents";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function invoke<T = any>(event: IpcEvents, ...args: any[]) {
 | 
				
			||||||
 | 
					    return ipcRenderer.invoke(event, ...args) as Promise<T>;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function sendSync<T = any>(event: IpcEvents, ...args: any[]) {
 | 
				
			||||||
 | 
					    return ipcRenderer.sendSync(event, ...args) as T;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										49
									
								
								src/renderer/appBadge.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,49 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * SPDX-License-Identifier: GPL-3.0
 | 
				
			||||||
 | 
					 * Modified for Aerocord, originally part of Vesktop, a desktop app aiming to give you a snappier Discord Experience
 | 
				
			||||||
 | 
					 * Copyright (c) 2024-2024 Aiek 
 | 
				
			||||||
 | 
					 * Copyright (c) 2024 RandomServer Community
 | 
				
			||||||
 | 
					 * Copyright (c) 2023 Vendicated and Vencord contributors
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { filters, waitFor } from "@vencord/types/webpack";
 | 
				
			||||||
 | 
					import { RelationshipStore } from "@vencord/types/webpack/common";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { Settings } from "./settings";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let GuildReadStateStore: any;
 | 
				
			||||||
 | 
					let NotificationSettingsStore: any;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function setBadge() {
 | 
				
			||||||
 | 
					    if (Settings.store.appBadge === false) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					        const mentionCount = GuildReadStateStore.getTotalMentionCount();
 | 
				
			||||||
 | 
					        const pendingRequests = RelationshipStore.getPendingCount();
 | 
				
			||||||
 | 
					        const hasUnread = GuildReadStateStore.hasAnyUnread();
 | 
				
			||||||
 | 
					        const disableUnreadBadge = NotificationSettingsStore.getDisableUnreadBadge();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let totalCount = mentionCount + pendingRequests;
 | 
				
			||||||
 | 
					        if (!totalCount && hasUnread && !disableUnreadBadge) totalCount = -1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        VesktopNative.app.setBadgeCount(totalCount);
 | 
				
			||||||
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					        console.error(e);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let toFind = 3;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function waitForAndSubscribeToStore(name: string, cb?: (m: any) => void) {
 | 
				
			||||||
 | 
					    waitFor(filters.byStoreName(name), store => {
 | 
				
			||||||
 | 
					        cb?.(store);
 | 
				
			||||||
 | 
					        store.addChangeListener(setBadge);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        toFind--;
 | 
				
			||||||
 | 
					        if (toFind === 0) setBadge();
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					waitForAndSubscribeToStore("GuildReadStateStore", store => (GuildReadStateStore = store));
 | 
				
			||||||
 | 
					waitForAndSubscribeToStore("NotificationSettingsStore", store => (NotificationSettingsStore = store));
 | 
				
			||||||
 | 
					waitForAndSubscribeToStore("RelationshipStore");
 | 
				
			||||||
							
								
								
									
										806
									
								
								src/renderer/components/ScreenSharePicker.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,806 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * SPDX-License-Identifier: GPL-3.0
 | 
				
			||||||
 | 
					 * Modified for Aerocord, originally part of Vesktop, a desktop app aiming to give you a snappier Discord Experience
 | 
				
			||||||
 | 
					 * Copyright (c) 2024-2024 Aiek 
 | 
				
			||||||
 | 
					 * Copyright (c) 2024 RandomServer Community
 | 
				
			||||||
 | 
					 * Copyright (c) 2023 Vendicated and Vencord contributors
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import "./screenSharePicker.css";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { closeModal, Logger, Modals, ModalSize, openModal, useAwaiter } from "@vencord/types/utils";
 | 
				
			||||||
 | 
					import { findStoreLazy, onceReady } from "@vencord/types/webpack";
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					    Button,
 | 
				
			||||||
 | 
					    Card,
 | 
				
			||||||
 | 
					    FluxDispatcher,
 | 
				
			||||||
 | 
					    Forms,
 | 
				
			||||||
 | 
					    Select,
 | 
				
			||||||
 | 
					    Switch,
 | 
				
			||||||
 | 
					    Text,
 | 
				
			||||||
 | 
					    UserStore,
 | 
				
			||||||
 | 
					    useState
 | 
				
			||||||
 | 
					} from "@vencord/types/webpack/common";
 | 
				
			||||||
 | 
					import { Node } from "@vencord/venmic";
 | 
				
			||||||
 | 
					import type { Dispatch, SetStateAction } from "react";
 | 
				
			||||||
 | 
					import { addPatch } from "renderer/patches/shared";
 | 
				
			||||||
 | 
					import { useSettings } from "renderer/settings";
 | 
				
			||||||
 | 
					import { isLinux, isWindows } from "renderer/utils";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const StreamResolutions = ["480", "720", "1080", "1440"] as const;
 | 
				
			||||||
 | 
					const StreamFps = ["15", "30", "60"] as const;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const MediaEngineStore = findStoreLazy("MediaEngineStore");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type StreamResolution = (typeof StreamResolutions)[number];
 | 
				
			||||||
 | 
					export type StreamFps = (typeof StreamFps)[number];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type SpecialSource = "None" | "Entire System";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type AudioSource = SpecialSource | Node;
 | 
				
			||||||
 | 
					type AudioSources = SpecialSource | Node[];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface AudioItem {
 | 
				
			||||||
 | 
					    name: string;
 | 
				
			||||||
 | 
					    value: AudioSource;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface StreamSettings {
 | 
				
			||||||
 | 
					    resolution: StreamResolution;
 | 
				
			||||||
 | 
					    fps: StreamFps;
 | 
				
			||||||
 | 
					    audio: boolean;
 | 
				
			||||||
 | 
					    contentHint?: string;
 | 
				
			||||||
 | 
					    includeSources?: AudioSources;
 | 
				
			||||||
 | 
					    excludeSources?: AudioSources;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface StreamPick extends StreamSettings {
 | 
				
			||||||
 | 
					    id: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface Source {
 | 
				
			||||||
 | 
					    id: string;
 | 
				
			||||||
 | 
					    name: string;
 | 
				
			||||||
 | 
					    url: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export let currentSettings: StreamSettings | null = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const logger = new Logger("VesktopScreenShare");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					addPatch({
 | 
				
			||||||
 | 
					    patches: [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            find: "this.localWant=",
 | 
				
			||||||
 | 
					            replacement: {
 | 
				
			||||||
 | 
					                match: /this.localWant=/,
 | 
				
			||||||
 | 
					                replace: "$self.patchStreamQuality(this);$&"
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    patchStreamQuality(opts: any) {
 | 
				
			||||||
 | 
					        if (!currentSettings) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const framerate = Number(currentSettings.fps);
 | 
				
			||||||
 | 
					        const height = Number(currentSettings.resolution);
 | 
				
			||||||
 | 
					        const width = Math.round(height * (16 / 9));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Object.assign(opts, {
 | 
				
			||||||
 | 
					            bitrateMin: 500000,
 | 
				
			||||||
 | 
					            bitrateMax: 8000000,
 | 
				
			||||||
 | 
					            bitrateTarget: 600000
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        if (opts?.encode) {
 | 
				
			||||||
 | 
					            Object.assign(opts.encode, {
 | 
				
			||||||
 | 
					                framerate,
 | 
				
			||||||
 | 
					                width,
 | 
				
			||||||
 | 
					                height,
 | 
				
			||||||
 | 
					                pixelCount: height * width
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        Object.assign(opts.capture, {
 | 
				
			||||||
 | 
					            framerate,
 | 
				
			||||||
 | 
					            width,
 | 
				
			||||||
 | 
					            height,
 | 
				
			||||||
 | 
					            pixelCount: height * width
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if (isLinux) {
 | 
				
			||||||
 | 
					    onceReady.then(() => {
 | 
				
			||||||
 | 
					        FluxDispatcher.subscribe("STREAM_CLOSE", ({ streamKey }: { streamKey: string }) => {
 | 
				
			||||||
 | 
					            const owner = streamKey.split(":").at(-1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (owner !== UserStore.getCurrentUser().id) {
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            VesktopNative.virtmic.stop();
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function openScreenSharePicker(screens: Source[], skipPicker: boolean) {
 | 
				
			||||||
 | 
					    let didSubmit = false;
 | 
				
			||||||
 | 
					    return new Promise<StreamPick>((resolve, reject) => {
 | 
				
			||||||
 | 
					        const key = openModal(
 | 
				
			||||||
 | 
					            props => (
 | 
				
			||||||
 | 
					                <ModalComponent
 | 
				
			||||||
 | 
					                    screens={screens}
 | 
				
			||||||
 | 
					                    modalProps={props}
 | 
				
			||||||
 | 
					                    submit={async v => {
 | 
				
			||||||
 | 
					                        didSubmit = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        if (v.includeSources && v.includeSources !== "None") {
 | 
				
			||||||
 | 
					                            if (v.includeSources === "Entire System") {
 | 
				
			||||||
 | 
					                                await VesktopNative.virtmic.startSystem(
 | 
				
			||||||
 | 
					                                    !v.excludeSources || isSpecialSource(v.excludeSources) ? [] : v.excludeSources
 | 
				
			||||||
 | 
					                                );
 | 
				
			||||||
 | 
					                            } else {
 | 
				
			||||||
 | 
					                                await VesktopNative.virtmic.start(v.includeSources);
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        resolve(v);
 | 
				
			||||||
 | 
					                    }}
 | 
				
			||||||
 | 
					                    close={() => {
 | 
				
			||||||
 | 
					                        props.onClose();
 | 
				
			||||||
 | 
					                        if (!didSubmit) reject("Aborted");
 | 
				
			||||||
 | 
					                    }}
 | 
				
			||||||
 | 
					                    skipPicker={skipPicker}
 | 
				
			||||||
 | 
					                />
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                onCloseRequest() {
 | 
				
			||||||
 | 
					                    closeModal(key);
 | 
				
			||||||
 | 
					                    reject("Aborted");
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function ScreenPicker({ screens, chooseScreen }: { screens: Source[]; chooseScreen: (id: string) => void }) {
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					        <div className="vcd-screen-picker-grid">
 | 
				
			||||||
 | 
					            {screens.map(({ id, name, url }) => (
 | 
				
			||||||
 | 
					                <label key={id}>
 | 
				
			||||||
 | 
					                    <input type="radio" name="screen" value={id} onChange={() => chooseScreen(id)} />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    <img src={url} alt="" />
 | 
				
			||||||
 | 
					                    <Text variant="text-sm/normal">{name}</Text>
 | 
				
			||||||
 | 
					                </label>
 | 
				
			||||||
 | 
					            ))}
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function AudioSettingsModal({
 | 
				
			||||||
 | 
					    modalProps,
 | 
				
			||||||
 | 
					    close,
 | 
				
			||||||
 | 
					    setAudioSources
 | 
				
			||||||
 | 
					}: {
 | 
				
			||||||
 | 
					    modalProps: any;
 | 
				
			||||||
 | 
					    close: () => void;
 | 
				
			||||||
 | 
					    setAudioSources: (s: AudioSources) => void;
 | 
				
			||||||
 | 
					}) {
 | 
				
			||||||
 | 
					    const Settings = useSettings();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					        <Modals.ModalRoot {...modalProps} size={ModalSize.MEDIUM}>
 | 
				
			||||||
 | 
					            <Modals.ModalHeader className="vcd-screen-picker-header">
 | 
				
			||||||
 | 
					                <Forms.FormTitle tag="h2">Venmic Settings</Forms.FormTitle>
 | 
				
			||||||
 | 
					                <Modals.ModalCloseButton onClick={close} />
 | 
				
			||||||
 | 
					            </Modals.ModalHeader>
 | 
				
			||||||
 | 
					            <Modals.ModalContent className="vcd-screen-picker-modal">
 | 
				
			||||||
 | 
					                <Switch
 | 
				
			||||||
 | 
					                    hideBorder
 | 
				
			||||||
 | 
					                    onChange={v => (Settings.audio = { ...Settings.audio, workaround: v })}
 | 
				
			||||||
 | 
					                    value={Settings.audio?.workaround ?? false}
 | 
				
			||||||
 | 
					                    note={
 | 
				
			||||||
 | 
					                        <>
 | 
				
			||||||
 | 
					                            Work around an issue that causes the microphone to be shared instead of the correct audio.
 | 
				
			||||||
 | 
					                            Only enable if you're experiencing this issue.
 | 
				
			||||||
 | 
					                        </>
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                >
 | 
				
			||||||
 | 
					                    Microphone Workaround
 | 
				
			||||||
 | 
					                </Switch>
 | 
				
			||||||
 | 
					                <Switch
 | 
				
			||||||
 | 
					                    hideBorder
 | 
				
			||||||
 | 
					                    onChange={v => (Settings.audio = { ...Settings.audio, onlySpeakers: v })}
 | 
				
			||||||
 | 
					                    value={Settings.audio?.onlySpeakers ?? true}
 | 
				
			||||||
 | 
					                    note={
 | 
				
			||||||
 | 
					                        <>
 | 
				
			||||||
 | 
					                            When sharing entire desktop audio, only share apps that play to a speaker. You may want to
 | 
				
			||||||
 | 
					                            disable this when using "mix bussing".
 | 
				
			||||||
 | 
					                        </>
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                >
 | 
				
			||||||
 | 
					                    Only Speakers
 | 
				
			||||||
 | 
					                </Switch>
 | 
				
			||||||
 | 
					                <Switch
 | 
				
			||||||
 | 
					                    hideBorder
 | 
				
			||||||
 | 
					                    onChange={v => (Settings.audio = { ...Settings.audio, onlyDefaultSpeakers: v })}
 | 
				
			||||||
 | 
					                    value={Settings.audio?.onlyDefaultSpeakers ?? true}
 | 
				
			||||||
 | 
					                    note={
 | 
				
			||||||
 | 
					                        <>
 | 
				
			||||||
 | 
					                            When sharing entire desktop audio, only share apps that play to the <b>default</b> speakers.
 | 
				
			||||||
 | 
					                            You may want to disable this when using "mix bussing".
 | 
				
			||||||
 | 
					                        </>
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                >
 | 
				
			||||||
 | 
					                    Only Default Speakers
 | 
				
			||||||
 | 
					                </Switch>
 | 
				
			||||||
 | 
					                <Switch
 | 
				
			||||||
 | 
					                    hideBorder
 | 
				
			||||||
 | 
					                    onChange={v => (Settings.audio = { ...Settings.audio, ignoreInputMedia: v })}
 | 
				
			||||||
 | 
					                    value={Settings.audio?.ignoreInputMedia ?? true}
 | 
				
			||||||
 | 
					                    note={<>Exclude nodes that are intended to capture audio.</>}
 | 
				
			||||||
 | 
					                >
 | 
				
			||||||
 | 
					                    Ignore Inputs
 | 
				
			||||||
 | 
					                </Switch>
 | 
				
			||||||
 | 
					                <Switch
 | 
				
			||||||
 | 
					                    hideBorder
 | 
				
			||||||
 | 
					                    onChange={v => (Settings.audio = { ...Settings.audio, ignoreVirtual: v })}
 | 
				
			||||||
 | 
					                    value={Settings.audio?.ignoreVirtual ?? false}
 | 
				
			||||||
 | 
					                    note={
 | 
				
			||||||
 | 
					                        <>
 | 
				
			||||||
 | 
					                            Exclude virtual nodes, such as nodes belonging to loopbacks. This might be useful when using
 | 
				
			||||||
 | 
					                            "mix bussing".
 | 
				
			||||||
 | 
					                        </>
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                >
 | 
				
			||||||
 | 
					                    Ignore Virtual
 | 
				
			||||||
 | 
					                </Switch>
 | 
				
			||||||
 | 
					                <Switch
 | 
				
			||||||
 | 
					                    hideBorder
 | 
				
			||||||
 | 
					                    onChange={v =>
 | 
				
			||||||
 | 
					                        (Settings.audio = {
 | 
				
			||||||
 | 
					                            ...Settings.audio,
 | 
				
			||||||
 | 
					                            ignoreDevices: v,
 | 
				
			||||||
 | 
					                            deviceSelect: v ? false : Settings.audio?.deviceSelect
 | 
				
			||||||
 | 
					                        })
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    value={Settings.audio?.ignoreDevices ?? true}
 | 
				
			||||||
 | 
					                    note={<>Exclude device nodes, such as nodes belonging to microphones or speakers.</>}
 | 
				
			||||||
 | 
					                >
 | 
				
			||||||
 | 
					                    Ignore Devices
 | 
				
			||||||
 | 
					                </Switch>
 | 
				
			||||||
 | 
					                <Switch
 | 
				
			||||||
 | 
					                    hideBorder
 | 
				
			||||||
 | 
					                    onChange={value => {
 | 
				
			||||||
 | 
					                        Settings.audio = { ...Settings.audio, granularSelect: value };
 | 
				
			||||||
 | 
					                        setAudioSources("None");
 | 
				
			||||||
 | 
					                    }}
 | 
				
			||||||
 | 
					                    value={Settings.audio?.granularSelect ?? false}
 | 
				
			||||||
 | 
					                    note={<>Allow to select applications more granularly.</>}
 | 
				
			||||||
 | 
					                >
 | 
				
			||||||
 | 
					                    Granular Selection
 | 
				
			||||||
 | 
					                </Switch>
 | 
				
			||||||
 | 
					                <Switch
 | 
				
			||||||
 | 
					                    hideBorder
 | 
				
			||||||
 | 
					                    onChange={value => {
 | 
				
			||||||
 | 
					                        Settings.audio = { ...Settings.audio, deviceSelect: value };
 | 
				
			||||||
 | 
					                        setAudioSources("None");
 | 
				
			||||||
 | 
					                    }}
 | 
				
			||||||
 | 
					                    value={Settings.audio?.deviceSelect ?? false}
 | 
				
			||||||
 | 
					                    disabled={Settings.audio?.ignoreDevices}
 | 
				
			||||||
 | 
					                    note={
 | 
				
			||||||
 | 
					                        <>
 | 
				
			||||||
 | 
					                            Allow to select devices such as microphones. Requires <b>Ignore Devices</b> to be turned
 | 
				
			||||||
 | 
					                            off.
 | 
				
			||||||
 | 
					                        </>
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                >
 | 
				
			||||||
 | 
					                    Device Selection
 | 
				
			||||||
 | 
					                </Switch>
 | 
				
			||||||
 | 
					            </Modals.ModalContent>
 | 
				
			||||||
 | 
					            <Modals.ModalFooter className="vcd-screen-picker-footer">
 | 
				
			||||||
 | 
					                <Button color={Button.Colors.TRANSPARENT} onClick={close}>
 | 
				
			||||||
 | 
					                    Back
 | 
				
			||||||
 | 
					                </Button>
 | 
				
			||||||
 | 
					            </Modals.ModalFooter>
 | 
				
			||||||
 | 
					        </Modals.ModalRoot>
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function StreamSettings({
 | 
				
			||||||
 | 
					    source,
 | 
				
			||||||
 | 
					    settings,
 | 
				
			||||||
 | 
					    setSettings,
 | 
				
			||||||
 | 
					    skipPicker
 | 
				
			||||||
 | 
					}: {
 | 
				
			||||||
 | 
					    source: Source;
 | 
				
			||||||
 | 
					    settings: StreamSettings;
 | 
				
			||||||
 | 
					    setSettings: Dispatch<SetStateAction<StreamSettings>>;
 | 
				
			||||||
 | 
					    skipPicker: boolean;
 | 
				
			||||||
 | 
					}) {
 | 
				
			||||||
 | 
					    const Settings = useSettings();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const [thumb] = useAwaiter(
 | 
				
			||||||
 | 
					        () => (skipPicker ? Promise.resolve(source.url) : VesktopNative.capturer.getLargeThumbnail(source.id)),
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            fallbackValue: source.url,
 | 
				
			||||||
 | 
					            deps: [source.id]
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const openSettings = () => {
 | 
				
			||||||
 | 
					        const key = openModal(props => (
 | 
				
			||||||
 | 
					            <AudioSettingsModal
 | 
				
			||||||
 | 
					                modalProps={props}
 | 
				
			||||||
 | 
					                close={() => props.onClose()}
 | 
				
			||||||
 | 
					                setAudioSources={sources =>
 | 
				
			||||||
 | 
					                    setSettings(s => ({ ...s, includeSources: sources, excludeSources: sources }))
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            />
 | 
				
			||||||
 | 
					        ));
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					        <div>
 | 
				
			||||||
 | 
					            <Forms.FormTitle>What you're streaming</Forms.FormTitle>
 | 
				
			||||||
 | 
					            <Card className="vcd-screen-picker-card vcd-screen-picker-preview">
 | 
				
			||||||
 | 
					                <img
 | 
				
			||||||
 | 
					                    src={thumb}
 | 
				
			||||||
 | 
					                    alt=""
 | 
				
			||||||
 | 
					                    className={isLinux ? "vcd-screen-picker-preview-img-linux" : "vcd-screen-picker-preview-img"}
 | 
				
			||||||
 | 
					                />
 | 
				
			||||||
 | 
					                <Text variant="text-sm/normal">{source.name}</Text>
 | 
				
			||||||
 | 
					            </Card>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <Forms.FormTitle>Stream Settings</Forms.FormTitle>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <Card className="vcd-screen-picker-card">
 | 
				
			||||||
 | 
					                <div className="vcd-screen-picker-quality">
 | 
				
			||||||
 | 
					                    <section>
 | 
				
			||||||
 | 
					                        <Forms.FormTitle>Resolution</Forms.FormTitle>
 | 
				
			||||||
 | 
					                        <div className="vcd-screen-picker-radios">
 | 
				
			||||||
 | 
					                            {StreamResolutions.map(res => (
 | 
				
			||||||
 | 
					                                <label className="vcd-screen-picker-radio" data-checked={settings.resolution === res}>
 | 
				
			||||||
 | 
					                                    <Text variant="text-sm/bold">{res}</Text>
 | 
				
			||||||
 | 
					                                    <input
 | 
				
			||||||
 | 
					                                        type="radio"
 | 
				
			||||||
 | 
					                                        name="resolution"
 | 
				
			||||||
 | 
					                                        value={res}
 | 
				
			||||||
 | 
					                                        checked={settings.resolution === res}
 | 
				
			||||||
 | 
					                                        onChange={() => setSettings(s => ({ ...s, resolution: res }))}
 | 
				
			||||||
 | 
					                                    />
 | 
				
			||||||
 | 
					                                </label>
 | 
				
			||||||
 | 
					                            ))}
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                    </section>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    <section>
 | 
				
			||||||
 | 
					                        <Forms.FormTitle>Frame Rate</Forms.FormTitle>
 | 
				
			||||||
 | 
					                        <div className="vcd-screen-picker-radios">
 | 
				
			||||||
 | 
					                            {StreamFps.map(fps => (
 | 
				
			||||||
 | 
					                                <label className="vcd-screen-picker-radio" data-checked={settings.fps === fps}>
 | 
				
			||||||
 | 
					                                    <Text variant="text-sm/bold">{fps}</Text>
 | 
				
			||||||
 | 
					                                    <input
 | 
				
			||||||
 | 
					                                        type="radio"
 | 
				
			||||||
 | 
					                                        name="fps"
 | 
				
			||||||
 | 
					                                        value={fps}
 | 
				
			||||||
 | 
					                                        checked={settings.fps === fps}
 | 
				
			||||||
 | 
					                                        onChange={() => setSettings(s => ({ ...s, fps }))}
 | 
				
			||||||
 | 
					                                    />
 | 
				
			||||||
 | 
					                                </label>
 | 
				
			||||||
 | 
					                            ))}
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                    </section>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					                <div className="vcd-screen-picker-quality">
 | 
				
			||||||
 | 
					                    <section>
 | 
				
			||||||
 | 
					                        <Forms.FormTitle>Content Type</Forms.FormTitle>
 | 
				
			||||||
 | 
					                        <div>
 | 
				
			||||||
 | 
					                            <div className="vcd-screen-picker-radios">
 | 
				
			||||||
 | 
					                                <label
 | 
				
			||||||
 | 
					                                    className="vcd-screen-picker-radio"
 | 
				
			||||||
 | 
					                                    data-checked={settings.contentHint === "motion"}
 | 
				
			||||||
 | 
					                                >
 | 
				
			||||||
 | 
					                                    <Text variant="text-sm/bold">Prefer Smoothness</Text>
 | 
				
			||||||
 | 
					                                    <input
 | 
				
			||||||
 | 
					                                        type="radio"
 | 
				
			||||||
 | 
					                                        name="contenthint"
 | 
				
			||||||
 | 
					                                        value="motion"
 | 
				
			||||||
 | 
					                                        checked={settings.contentHint === "motion"}
 | 
				
			||||||
 | 
					                                        onChange={() => setSettings(s => ({ ...s, contentHint: "motion" }))}
 | 
				
			||||||
 | 
					                                    />
 | 
				
			||||||
 | 
					                                </label>
 | 
				
			||||||
 | 
					                                <label
 | 
				
			||||||
 | 
					                                    className="vcd-screen-picker-radio"
 | 
				
			||||||
 | 
					                                    data-checked={settings.contentHint === "detail"}
 | 
				
			||||||
 | 
					                                >
 | 
				
			||||||
 | 
					                                    <Text variant="text-sm/bold">Prefer Clarity</Text>
 | 
				
			||||||
 | 
					                                    <input
 | 
				
			||||||
 | 
					                                        type="radio"
 | 
				
			||||||
 | 
					                                        name="contenthint"
 | 
				
			||||||
 | 
					                                        value="detail"
 | 
				
			||||||
 | 
					                                        checked={settings.contentHint === "detail"}
 | 
				
			||||||
 | 
					                                        onChange={() => setSettings(s => ({ ...s, contentHint: "detail" }))}
 | 
				
			||||||
 | 
					                                    />
 | 
				
			||||||
 | 
					                                </label>
 | 
				
			||||||
 | 
					                            </div>
 | 
				
			||||||
 | 
					                            <div className="vcd-screen-picker-hint-description">
 | 
				
			||||||
 | 
					                                <p>
 | 
				
			||||||
 | 
					                                    Choosing "Prefer Clarity" will result in a significantly lower framerate in exchange
 | 
				
			||||||
 | 
					                                    for a much sharper and clearer image.
 | 
				
			||||||
 | 
					                                </p>
 | 
				
			||||||
 | 
					                            </div>
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                        {isWindows && (
 | 
				
			||||||
 | 
					                            <Switch
 | 
				
			||||||
 | 
					                                value={settings.audio}
 | 
				
			||||||
 | 
					                                onChange={checked => setSettings(s => ({ ...s, audio: checked }))}
 | 
				
			||||||
 | 
					                                hideBorder
 | 
				
			||||||
 | 
					                                className="vcd-screen-picker-audio"
 | 
				
			||||||
 | 
					                            >
 | 
				
			||||||
 | 
					                                Stream With Audio
 | 
				
			||||||
 | 
					                            </Switch>
 | 
				
			||||||
 | 
					                        )}
 | 
				
			||||||
 | 
					                    </section>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                {isLinux && (
 | 
				
			||||||
 | 
					                    <AudioSourcePickerLinux
 | 
				
			||||||
 | 
					                        openSettings={openSettings}
 | 
				
			||||||
 | 
					                        includeSources={settings.includeSources}
 | 
				
			||||||
 | 
					                        excludeSources={settings.excludeSources}
 | 
				
			||||||
 | 
					                        deviceSelect={Settings.audio?.deviceSelect}
 | 
				
			||||||
 | 
					                        granularSelect={Settings.audio?.granularSelect}
 | 
				
			||||||
 | 
					                        setIncludeSources={sources => setSettings(s => ({ ...s, includeSources: sources }))}
 | 
				
			||||||
 | 
					                        setExcludeSources={sources => setSettings(s => ({ ...s, excludeSources: sources }))}
 | 
				
			||||||
 | 
					                    />
 | 
				
			||||||
 | 
					                )}
 | 
				
			||||||
 | 
					            </Card>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function isSpecialSource(value?: AudioSource | AudioSources): value is SpecialSource {
 | 
				
			||||||
 | 
					    return typeof value === "string";
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function hasMatchingProps(value: Node, other: Node) {
 | 
				
			||||||
 | 
					    return Object.keys(value).every(key => value[key] === other[key]);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function mapToAudioItem(node: AudioSource, granularSelect?: boolean, deviceSelect?: boolean): AudioItem[] {
 | 
				
			||||||
 | 
					    if (isSpecialSource(node)) {
 | 
				
			||||||
 | 
					        return [{ name: node, value: node }];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const rtn: AudioItem[] = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const mediaClass = node["media.class"];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (mediaClass?.includes("Video") || mediaClass?.includes("Midi")) {
 | 
				
			||||||
 | 
					        return rtn;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!deviceSelect && node["device.id"]) {
 | 
				
			||||||
 | 
					        return rtn;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const name = node["application.name"];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (name) {
 | 
				
			||||||
 | 
					        rtn.push({ name: name, value: { "application.name": name } });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!granularSelect) {
 | 
				
			||||||
 | 
					        return rtn;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const rawName = node["node.name"];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!name) {
 | 
				
			||||||
 | 
					        rtn.push({ name: rawName, value: { "node.name": rawName } });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const binary = node["application.process.binary"];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!name && binary) {
 | 
				
			||||||
 | 
					        rtn.push({ name: binary, value: { "application.process.binary": binary } });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const pid = node["application.process.id"];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const first = rtn[0];
 | 
				
			||||||
 | 
					    const firstValues = first.value as Node;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (pid) {
 | 
				
			||||||
 | 
					        rtn.push({
 | 
				
			||||||
 | 
					            name: `${first.name} (${pid})`,
 | 
				
			||||||
 | 
					            value: { ...firstValues, "application.process.id": pid }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const mediaName = node["media.name"];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (mediaName) {
 | 
				
			||||||
 | 
					        rtn.push({
 | 
				
			||||||
 | 
					            name: `${first.name} [${mediaName}]`,
 | 
				
			||||||
 | 
					            value: { ...firstValues, "media.name": mediaName }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (mediaClass) {
 | 
				
			||||||
 | 
					        rtn.push({
 | 
				
			||||||
 | 
					            name: `${first.name} [${mediaClass}]`,
 | 
				
			||||||
 | 
					            value: { ...firstValues, "media.class": mediaClass }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return rtn;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function isItemSelected(sources?: AudioSources) {
 | 
				
			||||||
 | 
					    return (value: AudioSource) => {
 | 
				
			||||||
 | 
					        if (!sources) {
 | 
				
			||||||
 | 
					            return false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (isSpecialSource(sources) || isSpecialSource(value)) {
 | 
				
			||||||
 | 
					            return sources === value;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return sources.some(source => hasMatchingProps(source, value));
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function updateItems(setSources: (s: AudioSources) => void, sources?: AudioSources) {
 | 
				
			||||||
 | 
					    return (value: AudioSource) => {
 | 
				
			||||||
 | 
					        if (isSpecialSource(value)) {
 | 
				
			||||||
 | 
					            setSources(value);
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (isSpecialSource(sources)) {
 | 
				
			||||||
 | 
					            setSources([value]);
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (isItemSelected(sources)(value)) {
 | 
				
			||||||
 | 
					            setSources(sources?.filter(x => !hasMatchingProps(x, value)) ?? "None");
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        setSources([...(sources || []), value]);
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function AudioSourcePickerLinux({
 | 
				
			||||||
 | 
					    includeSources,
 | 
				
			||||||
 | 
					    excludeSources,
 | 
				
			||||||
 | 
					    deviceSelect,
 | 
				
			||||||
 | 
					    granularSelect,
 | 
				
			||||||
 | 
					    openSettings,
 | 
				
			||||||
 | 
					    setIncludeSources,
 | 
				
			||||||
 | 
					    setExcludeSources
 | 
				
			||||||
 | 
					}: {
 | 
				
			||||||
 | 
					    includeSources?: AudioSources;
 | 
				
			||||||
 | 
					    excludeSources?: AudioSources;
 | 
				
			||||||
 | 
					    deviceSelect?: boolean;
 | 
				
			||||||
 | 
					    granularSelect?: boolean;
 | 
				
			||||||
 | 
					    openSettings: () => void;
 | 
				
			||||||
 | 
					    setIncludeSources: (s: AudioSources) => void;
 | 
				
			||||||
 | 
					    setExcludeSources: (s: AudioSources) => void;
 | 
				
			||||||
 | 
					}) {
 | 
				
			||||||
 | 
					    const [sources, _, loading] = useAwaiter(() => VesktopNative.virtmic.list(), {
 | 
				
			||||||
 | 
					        fallbackValue: { ok: true, targets: [], hasPipewirePulse: true }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const hasPipewirePulse = sources.ok ? sources.hasPipewirePulse : true;
 | 
				
			||||||
 | 
					    const [ignorePulseWarning, setIgnorePulseWarning] = useState(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!sources.ok && sources.isGlibCxxOutdated) {
 | 
				
			||||||
 | 
					        return (
 | 
				
			||||||
 | 
					            <Forms.FormText>
 | 
				
			||||||
 | 
					                Failed to retrieve Audio Sources because your C++ library is too old to run
 | 
				
			||||||
 | 
					                <a href="https://github.com/Vencord/venmic" target="_blank">
 | 
				
			||||||
 | 
					                    venmic
 | 
				
			||||||
 | 
					                </a>
 | 
				
			||||||
 | 
					                . See{" "}
 | 
				
			||||||
 | 
					                <a href="https://gist.github.com/Vendicated/b655044ffbb16b2716095a448c6d827a" target="_blank">
 | 
				
			||||||
 | 
					                    this guide
 | 
				
			||||||
 | 
					                </a>{" "}
 | 
				
			||||||
 | 
					                for possible solutions.
 | 
				
			||||||
 | 
					            </Forms.FormText>
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!hasPipewirePulse && !ignorePulseWarning) {
 | 
				
			||||||
 | 
					        return (
 | 
				
			||||||
 | 
					            <Text variant="text-sm/normal">
 | 
				
			||||||
 | 
					                Could not find pipewire-pulse. See{" "}
 | 
				
			||||||
 | 
					                <a href="https://gist.github.com/the-spyke/2de98b22ff4f978ebf0650c90e82027e#install" target="_blank">
 | 
				
			||||||
 | 
					                    this guide
 | 
				
			||||||
 | 
					                </a>{" "}
 | 
				
			||||||
 | 
					                on how to switch to pipewire. <br />
 | 
				
			||||||
 | 
					                You can still continue, however, please{" "}
 | 
				
			||||||
 | 
					                <b>beware that you can only share audio of apps that are running under pipewire</b>.{" "}
 | 
				
			||||||
 | 
					                <a onClick={() => setIgnorePulseWarning(true)}>I know what I'm doing!</a>
 | 
				
			||||||
 | 
					            </Text>
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const specialSources: SpecialSource[] = ["None", "Entire System"] as const;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const uniqueName = (value: AudioItem, index: number, list: AudioItem[]) =>
 | 
				
			||||||
 | 
					        list.findIndex(x => x.name === value.name) === index;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const allSources = sources.ok
 | 
				
			||||||
 | 
					        ? [...specialSources, ...sources.targets]
 | 
				
			||||||
 | 
					              .map(target => mapToAudioItem(target, granularSelect, deviceSelect))
 | 
				
			||||||
 | 
					              .flat()
 | 
				
			||||||
 | 
					              .filter(uniqueName)
 | 
				
			||||||
 | 
					        : [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					        <>
 | 
				
			||||||
 | 
					            <div className={includeSources === "Entire System" ? "vcd-screen-picker-quality" : undefined}>
 | 
				
			||||||
 | 
					                <section>
 | 
				
			||||||
 | 
					                    <Forms.FormTitle>{loading ? "Loading Sources..." : "Audio Sources"}</Forms.FormTitle>
 | 
				
			||||||
 | 
					                    <Select
 | 
				
			||||||
 | 
					                        options={allSources.map(({ name, value }) => ({
 | 
				
			||||||
 | 
					                            label: name,
 | 
				
			||||||
 | 
					                            value: value,
 | 
				
			||||||
 | 
					                            default: name === "None"
 | 
				
			||||||
 | 
					                        }))}
 | 
				
			||||||
 | 
					                        isSelected={isItemSelected(includeSources)}
 | 
				
			||||||
 | 
					                        select={updateItems(setIncludeSources, includeSources)}
 | 
				
			||||||
 | 
					                        serialize={String}
 | 
				
			||||||
 | 
					                        popoutPosition="top"
 | 
				
			||||||
 | 
					                        closeOnSelect={false}
 | 
				
			||||||
 | 
					                    />
 | 
				
			||||||
 | 
					                </section>
 | 
				
			||||||
 | 
					                {includeSources === "Entire System" && (
 | 
				
			||||||
 | 
					                    <section>
 | 
				
			||||||
 | 
					                        <Forms.FormTitle>Exclude Sources</Forms.FormTitle>
 | 
				
			||||||
 | 
					                        <Select
 | 
				
			||||||
 | 
					                            options={allSources
 | 
				
			||||||
 | 
					                                .filter(x => x.name !== "Entire System")
 | 
				
			||||||
 | 
					                                .map(({ name, value }) => ({
 | 
				
			||||||
 | 
					                                    label: name,
 | 
				
			||||||
 | 
					                                    value: value,
 | 
				
			||||||
 | 
					                                    default: name === "None"
 | 
				
			||||||
 | 
					                                }))}
 | 
				
			||||||
 | 
					                            isSelected={isItemSelected(excludeSources)}
 | 
				
			||||||
 | 
					                            select={updateItems(setExcludeSources, excludeSources)}
 | 
				
			||||||
 | 
					                            serialize={String}
 | 
				
			||||||
 | 
					                            popoutPosition="top"
 | 
				
			||||||
 | 
					                            closeOnSelect={false}
 | 
				
			||||||
 | 
					                        />
 | 
				
			||||||
 | 
					                    </section>
 | 
				
			||||||
 | 
					                )}
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					            <Button
 | 
				
			||||||
 | 
					                color={Button.Colors.TRANSPARENT}
 | 
				
			||||||
 | 
					                onClick={openSettings}
 | 
				
			||||||
 | 
					                className="vcd-screen-picker-settings-button"
 | 
				
			||||||
 | 
					            >
 | 
				
			||||||
 | 
					                Open Audio Settings
 | 
				
			||||||
 | 
					            </Button>
 | 
				
			||||||
 | 
					        </>
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function ModalComponent({
 | 
				
			||||||
 | 
					    screens,
 | 
				
			||||||
 | 
					    modalProps,
 | 
				
			||||||
 | 
					    submit,
 | 
				
			||||||
 | 
					    close,
 | 
				
			||||||
 | 
					    skipPicker
 | 
				
			||||||
 | 
					}: {
 | 
				
			||||||
 | 
					    screens: Source[];
 | 
				
			||||||
 | 
					    modalProps: any;
 | 
				
			||||||
 | 
					    submit: (data: StreamPick) => void;
 | 
				
			||||||
 | 
					    close: () => void;
 | 
				
			||||||
 | 
					    skipPicker: boolean;
 | 
				
			||||||
 | 
					}) {
 | 
				
			||||||
 | 
					    const [selected, setSelected] = useState<string | undefined>(skipPicker ? screens[0].id : void 0);
 | 
				
			||||||
 | 
					    const [settings, setSettings] = useState<StreamSettings>({
 | 
				
			||||||
 | 
					        resolution: "720",
 | 
				
			||||||
 | 
					        fps: "30",
 | 
				
			||||||
 | 
					        contentHint: "motion",
 | 
				
			||||||
 | 
					        audio: true,
 | 
				
			||||||
 | 
					        includeSources: "None"
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					        <Modals.ModalRoot {...modalProps} size={ModalSize.MEDIUM}>
 | 
				
			||||||
 | 
					            <Modals.ModalHeader className="vcd-screen-picker-header">
 | 
				
			||||||
 | 
					                <Forms.FormTitle tag="h2">ScreenShare</Forms.FormTitle>
 | 
				
			||||||
 | 
					                <Modals.ModalCloseButton onClick={close} />
 | 
				
			||||||
 | 
					            </Modals.ModalHeader>
 | 
				
			||||||
 | 
					            <Modals.ModalContent className="vcd-screen-picker-modal">
 | 
				
			||||||
 | 
					                {!selected ? (
 | 
				
			||||||
 | 
					                    <ScreenPicker screens={screens} chooseScreen={setSelected} />
 | 
				
			||||||
 | 
					                ) : (
 | 
				
			||||||
 | 
					                    <StreamSettings
 | 
				
			||||||
 | 
					                        source={screens.find(s => s.id === selected)!}
 | 
				
			||||||
 | 
					                        settings={settings}
 | 
				
			||||||
 | 
					                        setSettings={setSettings}
 | 
				
			||||||
 | 
					                        skipPicker={skipPicker}
 | 
				
			||||||
 | 
					                    />
 | 
				
			||||||
 | 
					                )}
 | 
				
			||||||
 | 
					            </Modals.ModalContent>
 | 
				
			||||||
 | 
					            <Modals.ModalFooter className="vcd-screen-picker-footer">
 | 
				
			||||||
 | 
					                <Button
 | 
				
			||||||
 | 
					                    disabled={!selected}
 | 
				
			||||||
 | 
					                    onClick={() => {
 | 
				
			||||||
 | 
					                        currentSettings = settings;
 | 
				
			||||||
 | 
					                        try {
 | 
				
			||||||
 | 
					                            const frameRate = Number(settings.fps);
 | 
				
			||||||
 | 
					                            const height = Number(settings.resolution);
 | 
				
			||||||
 | 
					                            const width = Math.round(height * (16 / 9));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            const conn = [...MediaEngineStore.getMediaEngine().connections].find(
 | 
				
			||||||
 | 
					                                connection => connection.streamUserId === UserStore.getCurrentUser().id
 | 
				
			||||||
 | 
					                            );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            if (conn) {
 | 
				
			||||||
 | 
					                                conn.videoStreamParameters[0].maxFrameRate = frameRate;
 | 
				
			||||||
 | 
					                                conn.videoStreamParameters[0].maxResolution.height = height;
 | 
				
			||||||
 | 
					                                conn.videoStreamParameters[0].maxResolution.width = width;
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            submit({
 | 
				
			||||||
 | 
					                                id: selected!,
 | 
				
			||||||
 | 
					                                ...settings
 | 
				
			||||||
 | 
					                            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            setTimeout(async () => {
 | 
				
			||||||
 | 
					                                const conn = [...MediaEngineStore.getMediaEngine().connections].find(
 | 
				
			||||||
 | 
					                                    connection => connection.streamUserId === UserStore.getCurrentUser().id
 | 
				
			||||||
 | 
					                                );
 | 
				
			||||||
 | 
					                                if (!conn) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                                const track = conn.input.stream.getVideoTracks()[0];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                                const constraints = {
 | 
				
			||||||
 | 
					                                    ...track.getConstraints(),
 | 
				
			||||||
 | 
					                                    frameRate: { min: frameRate, ideal: frameRate },
 | 
				
			||||||
 | 
					                                    width: { min: 640, ideal: width, max: width },
 | 
				
			||||||
 | 
					                                    height: { min: 480, ideal: height, max: height },
 | 
				
			||||||
 | 
					                                    advanced: [{ width: width, height: height }],
 | 
				
			||||||
 | 
					                                    resizeMode: "none"
 | 
				
			||||||
 | 
					                                };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                                try {
 | 
				
			||||||
 | 
					                                    await track.applyConstraints(constraints);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                                    logger.info(
 | 
				
			||||||
 | 
					                                        "Applied constraints successfully. New constraints:",
 | 
				
			||||||
 | 
					                                        track.getConstraints()
 | 
				
			||||||
 | 
					                                    );
 | 
				
			||||||
 | 
					                                } catch (e) {
 | 
				
			||||||
 | 
					                                    logger.error("Failed to apply constraints.", e);
 | 
				
			||||||
 | 
					                                }
 | 
				
			||||||
 | 
					                            }, 100);
 | 
				
			||||||
 | 
					                        } catch (error) {
 | 
				
			||||||
 | 
					                            logger.error("Error while submitting stream.", error);
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        close();
 | 
				
			||||||
 | 
					                    }}
 | 
				
			||||||
 | 
					                >
 | 
				
			||||||
 | 
					                    Go Live
 | 
				
			||||||
 | 
					                </Button>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                {selected && !skipPicker ? (
 | 
				
			||||||
 | 
					                    <Button color={Button.Colors.TRANSPARENT} onClick={() => setSelected(void 0)}>
 | 
				
			||||||
 | 
					                        Back
 | 
				
			||||||
 | 
					                    </Button>
 | 
				
			||||||
 | 
					                ) : (
 | 
				
			||||||
 | 
					                    <Button color={Button.Colors.TRANSPARENT} onClick={close}>
 | 
				
			||||||
 | 
					                        Cancel
 | 
				
			||||||
 | 
					                    </Button>
 | 
				
			||||||
 | 
					                )}
 | 
				
			||||||
 | 
					            </Modals.ModalFooter>
 | 
				
			||||||
 | 
					        </Modals.ModalRoot>
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										9
									
								
								src/renderer/components/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,9 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * SPDX-License-Identifier: GPL-3.0
 | 
				
			||||||
 | 
					 * Modified for Aerocord, originally part of Vesktop, a desktop app aiming to give you a snappier Discord Experience
 | 
				
			||||||
 | 
					 * Copyright (c) 2024-2024 Aiek 
 | 
				
			||||||
 | 
					 * Copyright (c) 2024 RandomServer Community
 | 
				
			||||||
 | 
					 * Copyright (c) 2023 Vendicated and Vencord contributors
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export * as ScreenShare from "./ScreenSharePicker";
 | 
				
			||||||
							
								
								
									
										145
									
								
								src/renderer/components/screenSharePicker.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,145 @@
 | 
				
			|||||||
 | 
					.vcd-screen-picker-modal {
 | 
				
			||||||
 | 
					    padding: 1em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.vcd-screen-picker-header h1 {
 | 
				
			||||||
 | 
					    margin: 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.vcd-screen-picker-footer {
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    gap: 1em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.vcd-screen-picker-card {
 | 
				
			||||||
 | 
					    flex-grow: 1;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.vcd-screen-picker-grid {
 | 
				
			||||||
 | 
					    display: grid;
 | 
				
			||||||
 | 
					    grid-template-columns: 1fr 1fr;
 | 
				
			||||||
 | 
					    gap: 2em 1em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.vcd-screen-picker-grid input {
 | 
				
			||||||
 | 
					    appearance: none;
 | 
				
			||||||
 | 
					    cursor: pointer;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.vcd-screen-picker-selected img {
 | 
				
			||||||
 | 
					    border: 2px solid var(--brand-500);
 | 
				
			||||||
 | 
					    border-radius: 3px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.vcd-screen-picker-grid label {
 | 
				
			||||||
 | 
					    overflow: hidden;
 | 
				
			||||||
 | 
					    padding: 8px;
 | 
				
			||||||
 | 
					    cursor: pointer;
 | 
				
			||||||
 | 
					    display: grid;
 | 
				
			||||||
 | 
					    justify-items: center;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.vcd-screen-picker-grid label:hover {
 | 
				
			||||||
 | 
					    outline: 2px solid var(--brand-500);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.vcd-screen-picker-grid div {
 | 
				
			||||||
 | 
					    white-space: nowrap;
 | 
				
			||||||
 | 
					    text-overflow: ellipsis;
 | 
				
			||||||
 | 
					    overflow: hidden;
 | 
				
			||||||
 | 
					    text-align: center;
 | 
				
			||||||
 | 
					    font-weight: 600;
 | 
				
			||||||
 | 
					    margin-inline: 0.5em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.vcd-screen-picker-card {
 | 
				
			||||||
 | 
					    padding: 0.5em;
 | 
				
			||||||
 | 
					    box-sizing: border-box;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.vcd-screen-picker-preview-img-linux {
 | 
				
			||||||
 | 
					    width: 60%;
 | 
				
			||||||
 | 
					    margin-bottom: 0.5em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.vcd-screen-picker-preview-img {
 | 
				
			||||||
 | 
					    width: 90%;
 | 
				
			||||||
 | 
					    margin-bottom: 0.5em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.vcd-screen-picker-preview {
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    flex-direction: column;
 | 
				
			||||||
 | 
					    justify-content: center;
 | 
				
			||||||
 | 
					    align-items: center;
 | 
				
			||||||
 | 
					    margin-bottom: 1em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.vcd-screen-picker-radio input {
 | 
				
			||||||
 | 
					    display: none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.vcd-screen-picker-radio {
 | 
				
			||||||
 | 
					    background-color: var(--background-secondary);
 | 
				
			||||||
 | 
					    border: 1px solid var(--primary-800);
 | 
				
			||||||
 | 
					    padding: 0.3em;
 | 
				
			||||||
 | 
					    cursor: pointer;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.vcd-screen-picker-radio h2 {
 | 
				
			||||||
 | 
					    margin: 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.vcd-screen-picker-radio[data-checked="true"] {
 | 
				
			||||||
 | 
					    background-color: var(--brand-500);
 | 
				
			||||||
 | 
					    border-color: var(--brand-500);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.vcd-screen-picker-radio[data-checked="true"] h2 {
 | 
				
			||||||
 | 
					    color: var(--interactive-active);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.vcd-screen-picker-quality {
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    gap: 1em;
 | 
				
			||||||
 | 
					    margin-bottom: 0.5em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.vcd-screen-picker-quality section {
 | 
				
			||||||
 | 
					    flex: 1 1 auto;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.vcd-screen-picker-settings-button {
 | 
				
			||||||
 | 
					    margin-left: auto;
 | 
				
			||||||
 | 
					    margin-top: 0.3rem;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.vcd-screen-picker-radios {
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    width: 100%;
 | 
				
			||||||
 | 
					    border-radius: 3px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.vcd-screen-picker-radios label {
 | 
				
			||||||
 | 
					    flex: 1 1 auto;
 | 
				
			||||||
 | 
					    text-align: center;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.vcd-screen-picker-radios label:first-child {
 | 
				
			||||||
 | 
					    border-radius: 3px 0 0 3px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.vcd-screen-picker-radios label:last-child {
 | 
				
			||||||
 | 
					    border-radius: 0 3px 3px 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.vcd-screen-picker-audio {
 | 
				
			||||||
 | 
					    margin-bottom: 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.vcd-screen-picker-hint-description {
 | 
				
			||||||
 | 
					    color: var(--header-secondary);
 | 
				
			||||||
 | 
					    font-size: 14px;
 | 
				
			||||||
 | 
					    line-height: 20px;
 | 
				
			||||||
 | 
					    font-weight: 400;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										28
									
								
								src/renderer/components/settings/AutoStartToggle.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,28 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * SPDX-License-Identifier: GPL-3.0
 | 
				
			||||||
 | 
					 * Modified for Aerocord, originally part of Vesktop, a desktop app aiming to give you a snappier Discord Experience
 | 
				
			||||||
 | 
					 * Copyright (c) 2024-2024 Aiek 
 | 
				
			||||||
 | 
					 * Copyright (c) 2024 RandomServer Community
 | 
				
			||||||
 | 
					 * Copyright (c) 2023 Vendicated and Vencord contributors
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { Switch, useState } from "@vencord/types/webpack/common";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { SettingsComponent } from "./Settings";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const AutoStartToggle: SettingsComponent = () => {
 | 
				
			||||||
 | 
					    const [autoStartEnabled, setAutoStartEnabled] = useState(VesktopNative.autostart.isEnabled());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					        <Switch
 | 
				
			||||||
 | 
					            value={autoStartEnabled}
 | 
				
			||||||
 | 
					            onChange={async v => {
 | 
				
			||||||
 | 
					                await VesktopNative.autostart[v ? "enable" : "disable"]();
 | 
				
			||||||
 | 
					                setAutoStartEnabled(v);
 | 
				
			||||||
 | 
					            }}
 | 
				
			||||||
 | 
					            note="Automatically start Vesktop on computer start-up"
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					            Start With System
 | 
				
			||||||
 | 
					        </Switch>
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										28
									
								
								src/renderer/components/settings/DiscordBranchPicker.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,28 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * SPDX-License-Identifier: GPL-3.0
 | 
				
			||||||
 | 
					 * Modified for Aerocord, originally part of Vesktop, a desktop app aiming to give you a snappier Discord Experience
 | 
				
			||||||
 | 
					 * Copyright (c) 2024-2024 Aiek 
 | 
				
			||||||
 | 
					 * Copyright (c) 2024 RandomServer Community
 | 
				
			||||||
 | 
					 * Copyright (c) 2023 Vendicated and Vencord contributors
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { Select } from "@vencord/types/webpack/common";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { SettingsComponent } from "./Settings";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const DiscordBranchPicker: SettingsComponent = ({ settings }) => {
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					        <Select
 | 
				
			||||||
 | 
					            placeholder="Stable"
 | 
				
			||||||
 | 
					            options={[
 | 
				
			||||||
 | 
					                { label: "Stable", value: "stable", default: true },
 | 
				
			||||||
 | 
					                { label: "Canary", value: "canary" },
 | 
				
			||||||
 | 
					                { label: "PTB", value: "ptb" }
 | 
				
			||||||
 | 
					            ]}
 | 
				
			||||||
 | 
					            closeOnSelect={true}
 | 
				
			||||||
 | 
					            select={v => (settings.discordBranch = v)}
 | 
				
			||||||
 | 
					            isSelected={v => v === settings.discordBranch}
 | 
				
			||||||
 | 
					            serialize={s => s}
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										28
									
								
								src/renderer/components/settings/NotificationBadgeToggle.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,28 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * SPDX-License-Identifier: GPL-3.0
 | 
				
			||||||
 | 
					 * Modified for Aerocord, originally part of Vesktop, a desktop app aiming to give you a snappier Discord Experience
 | 
				
			||||||
 | 
					 * Copyright (c) 2024-2024 Aiek 
 | 
				
			||||||
 | 
					 * Copyright (c) 2024 RandomServer Community
 | 
				
			||||||
 | 
					 * Copyright (c) 2023 Vendicated and Vencord contributors
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { Switch } from "@vencord/types/webpack/common";
 | 
				
			||||||
 | 
					import { setBadge } from "renderer/appBadge";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { SettingsComponent } from "./Settings";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const NotificationBadgeToggle: SettingsComponent = ({ settings }) => {
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					        <Switch
 | 
				
			||||||
 | 
					            value={settings.appBadge ?? true}
 | 
				
			||||||
 | 
					            onChange={v => {
 | 
				
			||||||
 | 
					                settings.appBadge = v;
 | 
				
			||||||
 | 
					                if (v) setBadge();
 | 
				
			||||||
 | 
					                else VesktopNative.app.setBadgeCount(0);
 | 
				
			||||||
 | 
					            }}
 | 
				
			||||||
 | 
					            note="Show mention badge on the app icon"
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					            Notification Badge
 | 
				
			||||||
 | 
					        </Switch>
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										178
									
								
								src/renderer/components/settings/Settings.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,178 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * SPDX-License-Identifier: GPL-3.0
 | 
				
			||||||
 | 
					 * Modified for Aerocord, originally part of Vesktop, a desktop app aiming to give you a snappier Discord Experience
 | 
				
			||||||
 | 
					 * Copyright (c) 2024-2024 Aiek 
 | 
				
			||||||
 | 
					 * Copyright (c) 2024 RandomServer Community
 | 
				
			||||||
 | 
					 * Copyright (c) 2023 Vendicated and Vencord contributors
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import "./settings.css";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { Forms, Switch, Text } from "@vencord/types/webpack/common";
 | 
				
			||||||
 | 
					import { ComponentType } from "react";
 | 
				
			||||||
 | 
					import { Settings, useSettings } from "renderer/settings";
 | 
				
			||||||
 | 
					import { isMac, isWindows } from "renderer/utils";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { AutoStartToggle } from "./AutoStartToggle";
 | 
				
			||||||
 | 
					import { DiscordBranchPicker } from "./DiscordBranchPicker";
 | 
				
			||||||
 | 
					import { NotificationBadgeToggle } from "./NotificationBadgeToggle";
 | 
				
			||||||
 | 
					import { VencordLocationPicker } from "./VencordLocationPicker";
 | 
				
			||||||
 | 
					import { WindowsTransparencyControls } from "./WindowsTransparencyControls";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface BooleanSetting {
 | 
				
			||||||
 | 
					    key: keyof typeof Settings.store;
 | 
				
			||||||
 | 
					    title: string;
 | 
				
			||||||
 | 
					    description: string;
 | 
				
			||||||
 | 
					    defaultValue: boolean;
 | 
				
			||||||
 | 
					    disabled?(): boolean;
 | 
				
			||||||
 | 
					    invisible?(): boolean;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type SettingsComponent = ComponentType<{ settings: typeof Settings.store }>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const SettingsOptions: Record<string, Array<BooleanSetting | SettingsComponent>> = {
 | 
				
			||||||
 | 
					    "Discord Branch": [DiscordBranchPicker],
 | 
				
			||||||
 | 
					    "System Startup & Performance": [
 | 
				
			||||||
 | 
					        AutoStartToggle,
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            key: "hardwareAcceleration",
 | 
				
			||||||
 | 
					            title: "Hardware Acceleration",
 | 
				
			||||||
 | 
					            description: "Enable hardware acceleration",
 | 
				
			||||||
 | 
					            defaultValue: true
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    "User Interface": [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            key: "customTitleBar",
 | 
				
			||||||
 | 
					            title: "Discord Titlebar",
 | 
				
			||||||
 | 
					            description: "Use Discord's custom title bar instead of the native system one. Requires a full restart.",
 | 
				
			||||||
 | 
					            defaultValue: isWindows
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            key: "staticTitle",
 | 
				
			||||||
 | 
					            title: "Static Title",
 | 
				
			||||||
 | 
					            description: 'Makes the window title "Vesktop" instead of changing to the current page',
 | 
				
			||||||
 | 
					            defaultValue: false
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            key: "enableMenu",
 | 
				
			||||||
 | 
					            title: "Enable Menu Bar",
 | 
				
			||||||
 | 
					            description: "Enables the application menu bar. Press ALT to toggle visibility.",
 | 
				
			||||||
 | 
					            defaultValue: false,
 | 
				
			||||||
 | 
					            disabled: () => Settings.store.customTitleBar ?? isWindows
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            key: "splashTheming",
 | 
				
			||||||
 | 
					            title: "Splash theming",
 | 
				
			||||||
 | 
					            description: "Adapt the splash window colors to your custom theme",
 | 
				
			||||||
 | 
					            defaultValue: false
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        WindowsTransparencyControls
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    Behaviour: [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            key: "tray",
 | 
				
			||||||
 | 
					            title: "Tray Icon",
 | 
				
			||||||
 | 
					            description: "Add a tray icon for Vesktop",
 | 
				
			||||||
 | 
					            defaultValue: true,
 | 
				
			||||||
 | 
					            invisible: () => isMac
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            key: "minimizeToTray",
 | 
				
			||||||
 | 
					            title: "Minimize to tray",
 | 
				
			||||||
 | 
					            description: "Hitting X will make Vesktop minimize to the tray instead of closing",
 | 
				
			||||||
 | 
					            defaultValue: true,
 | 
				
			||||||
 | 
					            invisible: () => isMac,
 | 
				
			||||||
 | 
					            disabled: () => Settings.store.tray === false
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            key: "clickTrayToShowHide",
 | 
				
			||||||
 | 
					            title: "Hide/Show on tray click",
 | 
				
			||||||
 | 
					            description: "Left clicking tray icon will toggle the vesktop window visibility.",
 | 
				
			||||||
 | 
					            defaultValue: false
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            key: "disableMinSize",
 | 
				
			||||||
 | 
					            title: "Disable minimum window size",
 | 
				
			||||||
 | 
					            description: "Allows you to make the window as small as your heart desires",
 | 
				
			||||||
 | 
					            defaultValue: false
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            key: "disableSmoothScroll",
 | 
				
			||||||
 | 
					            title: "Disable smooth scrolling",
 | 
				
			||||||
 | 
					            description: "Disables smooth scrolling",
 | 
				
			||||||
 | 
					            defaultValue: false
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    "Notifications & Updates": [
 | 
				
			||||||
 | 
					        NotificationBadgeToggle,
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            key: "checkUpdates",
 | 
				
			||||||
 | 
					            title: "Check for updates",
 | 
				
			||||||
 | 
					            description: "Automatically check for Vesktop updates",
 | 
				
			||||||
 | 
					            defaultValue: true
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    Miscellaneous: [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            key: "arRPC",
 | 
				
			||||||
 | 
					            title: "Rich Presence",
 | 
				
			||||||
 | 
					            description: "Enables Rich Presence via arRPC",
 | 
				
			||||||
 | 
					            defaultValue: false
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            key: "openLinksWithElectron",
 | 
				
			||||||
 | 
					            title: "Open Links in app (experimental)",
 | 
				
			||||||
 | 
					            description: "Opens links in a new Vesktop window instead of your web browser",
 | 
				
			||||||
 | 
					            defaultValue: false
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    "Vencord Location": [VencordLocationPicker]
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function SettingsSections() {
 | 
				
			||||||
 | 
					    const Settings = useSettings();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const sections = Object.entries(SettingsOptions).map(([title, settings]) => (
 | 
				
			||||||
 | 
					        <Forms.FormSection
 | 
				
			||||||
 | 
					            title={title}
 | 
				
			||||||
 | 
					            key={title}
 | 
				
			||||||
 | 
					            className="vcd-settings-section"
 | 
				
			||||||
 | 
					            titleClassName="vcd-settings-title"
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					            {settings.map(Setting => {
 | 
				
			||||||
 | 
					                if (typeof Setting === "function") return <Setting settings={Settings} />;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                const { defaultValue, title, description, key, disabled, invisible } = Setting;
 | 
				
			||||||
 | 
					                if (invisible?.()) return null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                return (
 | 
				
			||||||
 | 
					                    <Switch
 | 
				
			||||||
 | 
					                        value={Settings[key as any] ?? defaultValue}
 | 
				
			||||||
 | 
					                        onChange={v => (Settings[key as any] = v)}
 | 
				
			||||||
 | 
					                        note={description}
 | 
				
			||||||
 | 
					                        disabled={disabled?.()}
 | 
				
			||||||
 | 
					                        key={key}
 | 
				
			||||||
 | 
					                    >
 | 
				
			||||||
 | 
					                        {title}
 | 
				
			||||||
 | 
					                    </Switch>
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					            })}
 | 
				
			||||||
 | 
					        </Forms.FormSection>
 | 
				
			||||||
 | 
					    ));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return <>{sections}</>;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default function SettingsUi() {
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					        <Forms.FormSection>
 | 
				
			||||||
 | 
					            <Text variant="heading-lg/semibold" style={{ color: "var(--header-primary)" }} tag="h2">
 | 
				
			||||||
 | 
					                Vesktop Settings
 | 
				
			||||||
 | 
					            </Text>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <SettingsSections />
 | 
				
			||||||
 | 
					        </Forms.FormSection>
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										78
									
								
								src/renderer/components/settings/VencordLocationPicker.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,78 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * SPDX-License-Identifier: GPL-3.0
 | 
				
			||||||
 | 
					 * Modified for Aerocord, originally part of Vesktop, a desktop app aiming to give you a snappier Discord Experience
 | 
				
			||||||
 | 
					 * Copyright (c) 2024-2024 Aiek 
 | 
				
			||||||
 | 
					 * Copyright (c) 2024 RandomServer Community
 | 
				
			||||||
 | 
					 * Copyright (c) 2023 Vendicated and Vencord contributors
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { useForceUpdater } from "@vencord/types/utils";
 | 
				
			||||||
 | 
					import { Button, Forms, Toasts } from "@vencord/types/webpack/common";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { SettingsComponent } from "./Settings";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const VencordLocationPicker: SettingsComponent = ({ settings }) => {
 | 
				
			||||||
 | 
					    const forceUpdate = useForceUpdater();
 | 
				
			||||||
 | 
					    const vencordDir = VesktopNative.fileManager.getVencordDir();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					        <>
 | 
				
			||||||
 | 
					            <Forms.FormText>
 | 
				
			||||||
 | 
					                Vencord files are loaded from{" "}
 | 
				
			||||||
 | 
					                {vencordDir ? (
 | 
				
			||||||
 | 
					                    <a
 | 
				
			||||||
 | 
					                        href="about:blank"
 | 
				
			||||||
 | 
					                        onClick={e => {
 | 
				
			||||||
 | 
					                            e.preventDefault();
 | 
				
			||||||
 | 
					                            VesktopNative.fileManager.showItemInFolder(vencordDir!);
 | 
				
			||||||
 | 
					                        }}
 | 
				
			||||||
 | 
					                    >
 | 
				
			||||||
 | 
					                        {vencordDir}
 | 
				
			||||||
 | 
					                    </a>
 | 
				
			||||||
 | 
					                ) : (
 | 
				
			||||||
 | 
					                    "the default location"
 | 
				
			||||||
 | 
					                )}
 | 
				
			||||||
 | 
					            </Forms.FormText>
 | 
				
			||||||
 | 
					            <div className="vcd-location-btns">
 | 
				
			||||||
 | 
					                <Button
 | 
				
			||||||
 | 
					                    size={Button.Sizes.SMALL}
 | 
				
			||||||
 | 
					                    onClick={async () => {
 | 
				
			||||||
 | 
					                        const choice = await VesktopNative.fileManager.selectVencordDir();
 | 
				
			||||||
 | 
					                        switch (choice) {
 | 
				
			||||||
 | 
					                            case "cancelled":
 | 
				
			||||||
 | 
					                                break;
 | 
				
			||||||
 | 
					                            case "ok":
 | 
				
			||||||
 | 
					                                Toasts.show({
 | 
				
			||||||
 | 
					                                    message: "Vencord install changed. Fully restart Vesktop to apply.",
 | 
				
			||||||
 | 
					                                    id: Toasts.genId(),
 | 
				
			||||||
 | 
					                                    type: Toasts.Type.SUCCESS
 | 
				
			||||||
 | 
					                                });
 | 
				
			||||||
 | 
					                                break;
 | 
				
			||||||
 | 
					                            case "invalid":
 | 
				
			||||||
 | 
					                                Toasts.show({
 | 
				
			||||||
 | 
					                                    message:
 | 
				
			||||||
 | 
					                                        "You did not choose a valid Vencord install. Make sure you're selecting the dist dir!",
 | 
				
			||||||
 | 
					                                    id: Toasts.genId(),
 | 
				
			||||||
 | 
					                                    type: Toasts.Type.FAILURE
 | 
				
			||||||
 | 
					                                });
 | 
				
			||||||
 | 
					                                break;
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                        forceUpdate();
 | 
				
			||||||
 | 
					                    }}
 | 
				
			||||||
 | 
					                >
 | 
				
			||||||
 | 
					                    Change
 | 
				
			||||||
 | 
					                </Button>
 | 
				
			||||||
 | 
					                <Button
 | 
				
			||||||
 | 
					                    size={Button.Sizes.SMALL}
 | 
				
			||||||
 | 
					                    color={Button.Colors.RED}
 | 
				
			||||||
 | 
					                    onClick={async () => {
 | 
				
			||||||
 | 
					                        await VesktopNative.fileManager.selectVencordDir(null);
 | 
				
			||||||
 | 
					                        forceUpdate();
 | 
				
			||||||
 | 
					                    }}
 | 
				
			||||||
 | 
					                >
 | 
				
			||||||
 | 
					                    Reset
 | 
				
			||||||
 | 
					                </Button>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        </>
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@ -0,0 +1,51 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * SPDX-License-Identifier: GPL-3.0
 | 
				
			||||||
 | 
					 * Modified for Aerocord, originally part of Vesktop, a desktop app aiming to give you a snappier Discord Experience
 | 
				
			||||||
 | 
					 * Copyright (c) 2024-2024 Aiek 
 | 
				
			||||||
 | 
					 * Copyright (c) 2024 RandomServer Community
 | 
				
			||||||
 | 
					 * Copyright (c) 2023 Vendicated and Vencord contributors
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { Margins } from "@vencord/types/utils";
 | 
				
			||||||
 | 
					import { Forms, Select } from "@vencord/types/webpack/common";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { SettingsComponent } from "./Settings";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const WindowsTransparencyControls: SettingsComponent = ({ settings }) => {
 | 
				
			||||||
 | 
					    if (!VesktopNative.app.supportsWindowsTransparency()) return null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					        <>
 | 
				
			||||||
 | 
					            <Forms.FormTitle className={Margins.top16 + " " + Margins.bottom8}>Transparency Options</Forms.FormTitle>
 | 
				
			||||||
 | 
					            <Forms.FormText className={Margins.bottom8}>
 | 
				
			||||||
 | 
					                Requires a full restart. You will need a theme that supports transparency for this to work.
 | 
				
			||||||
 | 
					            </Forms.FormText>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <Select
 | 
				
			||||||
 | 
					                placeholder="None"
 | 
				
			||||||
 | 
					                options={[
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        label: "None",
 | 
				
			||||||
 | 
					                        value: "none",
 | 
				
			||||||
 | 
					                        default: true
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        label: "Mica (incorporates system theme + desktop wallpaper to paint the background)",
 | 
				
			||||||
 | 
					                        value: "mica"
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    { label: "Tabbed (variant of Mica with stronger background tinting)", value: "tabbed" },
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        label: "Acrylic (blurs the window behind Vesktop for a translucent background)",
 | 
				
			||||||
 | 
					                        value: "acrylic"
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                ]}
 | 
				
			||||||
 | 
					                closeOnSelect={true}
 | 
				
			||||||
 | 
					                select={v => (settings.transparencyOption = v)}
 | 
				
			||||||
 | 
					                isSelected={v => v === settings.transparencyOption}
 | 
				
			||||||
 | 
					                serialize={s => s}
 | 
				
			||||||
 | 
					            />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <Forms.FormDivider className={Margins.top16 + " " + Margins.bottom16} />
 | 
				
			||||||
 | 
					        </>
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										14
									
								
								src/renderer/components/settings/settings.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,14 @@
 | 
				
			|||||||
 | 
					.vcd-location-btns {
 | 
				
			||||||
 | 
					    display: grid;
 | 
				
			||||||
 | 
					    grid-template-columns: 1fr 1fr;
 | 
				
			||||||
 | 
					    gap: 0.5em;
 | 
				
			||||||
 | 
					    margin-top: 0.5em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.vcd-settings-section {
 | 
				
			||||||
 | 
					    margin-top: 1.5rem;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.vcd-settings-title {
 | 
				
			||||||
 | 
					    margin-bottom: 0.5rem;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										16
									
								
								src/renderer/fixes.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,16 @@
 | 
				
			|||||||
 | 
					/* Download Desktop button in guilds list */
 | 
				
			||||||
 | 
					[class^=listItem_]:has([data-list-item-id=guildsnav___app-download-button]),
 | 
				
			||||||
 | 
					[class^=listItem_]:has(+ [class^=listItem_] [data-list-item-id=guildsnav___app-download-button]) {
 | 
				
			||||||
 | 
					    display: none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* FIXME: remove this once Discord fixes their css to not explode scrollbars on chromium >=121 */
 | 
				
			||||||
 | 
					* {
 | 
				
			||||||
 | 
					    scrollbar-width: unset !important;
 | 
				
			||||||
 | 
					    scrollbar-color: unset !important;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Workaround for making things in the draggable area clickable again on macOS */
 | 
				
			||||||
 | 
					.platform-osx [class*=topic_], .platform-osx [class*=notice_] button {
 | 
				
			||||||
 | 
					    -webkit-app-region: no-drag;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										37
									
								
								src/renderer/fixes.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,37 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * SPDX-License-Identifier: GPL-3.0
 | 
				
			||||||
 | 
					 * Modified for Aerocord, originally part of Vesktop, a desktop app aiming to give you a snappier Discord Experience
 | 
				
			||||||
 | 
					 * Copyright (c) 2024-2024 Aiek 
 | 
				
			||||||
 | 
					 * Copyright (c) 2024 RandomServer Community
 | 
				
			||||||
 | 
					 * Copyright (c) 2023 Vendicated and Vencord contributors
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import "./fixes.css";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { isWindows, localStorage } from "./utils";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Make clicking Notifications focus the window
 | 
				
			||||||
 | 
					const originalSetOnClick = Object.getOwnPropertyDescriptor(Notification.prototype, "onclick")!.set!;
 | 
				
			||||||
 | 
					Object.defineProperty(Notification.prototype, "onclick", {
 | 
				
			||||||
 | 
					    set(onClick) {
 | 
				
			||||||
 | 
					        originalSetOnClick.call(this, function (this: unknown) {
 | 
				
			||||||
 | 
					            onClick.apply(this, arguments);
 | 
				
			||||||
 | 
					            VesktopNative.win.focus();
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    configurable: true
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Hide "Download Discord Desktop now!!!!" banner
 | 
				
			||||||
 | 
					localStorage.setItem("hideNag", "true");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// FIXME: Remove eventually.
 | 
				
			||||||
 | 
					// Originally, Vencord always used a Windows user agent. This seems to cause captchas
 | 
				
			||||||
 | 
					// Now, we use a platform specific UA - HOWEVER, discord FOR SOME REASON????? caches
 | 
				
			||||||
 | 
					// device props in localStorage. This code fixes their cache to properly update the platform in SuperProps
 | 
				
			||||||
 | 
					if (!isWindows)
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					        const deviceProperties = localStorage.getItem("deviceProperties");
 | 
				
			||||||
 | 
					        if (deviceProperties && JSON.parse(deviceProperties).os === "Windows")
 | 
				
			||||||
 | 
					            localStorage.removeItem("deviceProperties");
 | 
				
			||||||
 | 
					    } catch {}
 | 
				
			||||||
							
								
								
									
										79
									
								
								src/renderer/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,79 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * SPDX-License-Identifier: GPL-3.0
 | 
				
			||||||
 | 
					 * Modified for Aerocord, originally part of Vesktop, a desktop app aiming to give you a snappier Discord Experience
 | 
				
			||||||
 | 
					 * Copyright (c) 2024-2024 Aiek 
 | 
				
			||||||
 | 
					 * Copyright (c) 2024 RandomServer Community
 | 
				
			||||||
 | 
					 * Copyright (c) 2023 Vendicated and Vencord contributors
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import "./fixes";
 | 
				
			||||||
 | 
					import "./appBadge";
 | 
				
			||||||
 | 
					import "./patches";
 | 
				
			||||||
 | 
					import "./themedSplash";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					console.log("read if cute :3");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export * as Components from "./components";
 | 
				
			||||||
 | 
					import { findByPropsLazy, onceReady } from "@vencord/types/webpack";
 | 
				
			||||||
 | 
					import { Alerts, FluxDispatcher } from "@vencord/types/webpack/common";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import SettingsUi from "./components/settings/Settings";
 | 
				
			||||||
 | 
					import { Settings } from "./settings";
 | 
				
			||||||
 | 
					export { Settings };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const InviteActions = findByPropsLazy("resolveInvite");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export async function openInviteModal(code: string) {
 | 
				
			||||||
 | 
					    const { invite } = await InviteActions.resolveInvite(code, "Desktop Modal");
 | 
				
			||||||
 | 
					    if (!invite) return false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    VesktopNative.win.focus();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    FluxDispatcher.dispatch({
 | 
				
			||||||
 | 
					        type: "INVITE_MODAL_OPEN",
 | 
				
			||||||
 | 
					        invite,
 | 
				
			||||||
 | 
					        code,
 | 
				
			||||||
 | 
					        context: "APP"
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return true;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const customSettingsSections = (
 | 
				
			||||||
 | 
					    Vencord.Plugins.plugins.Settings as any as { customSections: ((ID: Record<string, unknown>) => any)[] }
 | 
				
			||||||
 | 
					).customSections;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					customSettingsSections.push(() => ({
 | 
				
			||||||
 | 
					    section: "Vesktop",
 | 
				
			||||||
 | 
					    label: "Vesktop Settings",
 | 
				
			||||||
 | 
					    element: SettingsUi,
 | 
				
			||||||
 | 
					    className: "vc-vesktop-settings"
 | 
				
			||||||
 | 
					}));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const arRPC = Vencord.Plugins.plugins["WebRichPresence (arRPC)"] as any as {
 | 
				
			||||||
 | 
					    handleEvent(e: MessageEvent): void;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					VesktopNative.arrpc.onActivity(async data => {
 | 
				
			||||||
 | 
					    if (!Settings.store.arRPC) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await onceReady;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    arRPC.handleEvent(new MessageEvent("message", { data }));
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TODO: remove soon
 | 
				
			||||||
 | 
					const vencordDir = "vencordDir" as keyof typeof Settings.store;
 | 
				
			||||||
 | 
					if (Settings.store[vencordDir]) {
 | 
				
			||||||
 | 
					    onceReady.then(() =>
 | 
				
			||||||
 | 
					        setTimeout(
 | 
				
			||||||
 | 
					            () =>
 | 
				
			||||||
 | 
					                Alerts.show({
 | 
				
			||||||
 | 
					                    title: "Custom Vencord Location",
 | 
				
			||||||
 | 
					                    body: "Due to security hardening changes in Vesktop, your custom Vencord location had to be reset. Please configure it again in the settings.",
 | 
				
			||||||
 | 
					                    onConfirm: () => delete Settings.store[vencordDir]
 | 
				
			||||||
 | 
					                }),
 | 
				
			||||||
 | 
					            5000
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										23
									
								
								src/renderer/patches/enableNotificationsByDefault.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,23 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * SPDX-License-Identifier: GPL-3.0
 | 
				
			||||||
 | 
					 * Modified for Aerocord, originally part of Vesktop, a desktop app aiming to give you a snappier Discord Experience
 | 
				
			||||||
 | 
					 * Copyright (c) 2024-2024 Aiek 
 | 
				
			||||||
 | 
					 * Copyright (c) 2024 RandomServer Community
 | 
				
			||||||
 | 
					 * Copyright (c) 2023 Vendicated and Vencord contributors
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { addPatch } from "./shared";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					addPatch({
 | 
				
			||||||
 | 
					    patches: [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            find: '"NotificationSettingsStore',
 | 
				
			||||||
 | 
					            replacement: {
 | 
				
			||||||
 | 
					                // FIXME: fix eslint rule
 | 
				
			||||||
 | 
					                // eslint-disable-next-line no-useless-escape
 | 
				
			||||||
 | 
					                match: /\.isPlatformEmbedded(?=\?\i\.\i\.ALL)/g,
 | 
				
			||||||
 | 
					                replace: "$&||true"
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										26
									
								
								src/renderer/patches/hideSwitchDevice.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,26 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * SPDX-License-Identifier: GPL-3.0
 | 
				
			||||||
 | 
					 * Modified for Aerocord, originally part of Vesktop, a desktop app aiming to give you a snappier Discord Experience
 | 
				
			||||||
 | 
					 * Copyright (c) 2024-2024 Aiek 
 | 
				
			||||||
 | 
					 * Copyright (c) 2024 RandomServer Community
 | 
				
			||||||
 | 
					 * Copyright (c) 2023 Vendicated and Vencord contributors
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { addPatch } from "./shared";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					addPatch({
 | 
				
			||||||
 | 
					    patches: [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            find: "lastOutputSystemDevice.justChanged",
 | 
				
			||||||
 | 
					            replacement: {
 | 
				
			||||||
 | 
					                // eslint-disable-next-line no-useless-escape
 | 
				
			||||||
 | 
					                match: /(\i)\.\i\.getState\(\).neverShowModal/,
 | 
				
			||||||
 | 
					                replace: "$& || $self.shouldIgnore($1)"
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    shouldIgnore(state: any) {
 | 
				
			||||||
 | 
					        return Object.keys(state?.default?.lastDeviceConnected ?? {})?.[0] === "vencord-screen-share";
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										27
									
								
								src/renderer/patches/hideVenmicInput.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,27 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * SPDX-License-Identifier: GPL-3.0
 | 
				
			||||||
 | 
					 * Modified for Aerocord, originally part of Vesktop, a desktop app aiming to give you a snappier Discord Experience
 | 
				
			||||||
 | 
					 * Copyright (c) 2024-2024 Aiek 
 | 
				
			||||||
 | 
					 * Copyright (c) 2024 RandomServer Community
 | 
				
			||||||
 | 
					 * Copyright (c) 2023 Vendicated and Vencord contributors
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { addPatch } from "./shared";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					addPatch({
 | 
				
			||||||
 | 
					    patches: [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            find: 'setSinkId"in',
 | 
				
			||||||
 | 
					            replacement: {
 | 
				
			||||||
 | 
					                // eslint-disable-next-line no-useless-escape
 | 
				
			||||||
 | 
					                match: /return (\i)\?navigator\.mediaDevices\.enumerateDevices/,
 | 
				
			||||||
 | 
					                replace: "return $1 ? $self.filteredDevices"
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async filteredDevices() {
 | 
				
			||||||
 | 
					        const original = await navigator.mediaDevices.enumerateDevices();
 | 
				
			||||||
 | 
					        return original.filter(x => x.label !== "vencord-screen-share");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										16
									
								
								src/renderer/patches/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,16 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * SPDX-License-Identifier: GPL-3.0
 | 
				
			||||||
 | 
					 * Modified for Aerocord, originally part of Vesktop, a desktop app aiming to give you a snappier Discord Experience
 | 
				
			||||||
 | 
					 * Copyright (c) 2024-2024 Aiek 
 | 
				
			||||||
 | 
					 * Copyright (c) 2024 RandomServer Community
 | 
				
			||||||
 | 
					 * Copyright (c) 2023 Vendicated and Vencord contributors
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TODO: Possibly auto generate glob if we have more patches in the future
 | 
				
			||||||
 | 
					import "./enableNotificationsByDefault";
 | 
				
			||||||
 | 
					import "./platformClass";
 | 
				
			||||||
 | 
					import "./hideSwitchDevice";
 | 
				
			||||||
 | 
					import "./hideVenmicInput";
 | 
				
			||||||
 | 
					import "./screenShareFixes";
 | 
				
			||||||
 | 
					import "./spellCheck";
 | 
				
			||||||
 | 
					import "./windowsTitleBar";
 | 
				
			||||||
							
								
								
									
										31
									
								
								src/renderer/patches/platformClass.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,31 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * SPDX-License-Identifier: GPL-3.0
 | 
				
			||||||
 | 
					 * Modified for Aerocord, originally part of Vesktop, a desktop app aiming to give you a snappier Discord Experience
 | 
				
			||||||
 | 
					 * Copyright (c) 2024-2024 Aiek 
 | 
				
			||||||
 | 
					 * Copyright (c) 2024 RandomServer Community
 | 
				
			||||||
 | 
					 * Copyright (c) 2023 Vendicated and Vencord contributors
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { Settings } from "renderer/settings";
 | 
				
			||||||
 | 
					import { isMac } from "renderer/utils";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { addPatch } from "./shared";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					addPatch({
 | 
				
			||||||
 | 
					    patches: [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            find: "platform-web",
 | 
				
			||||||
 | 
					            replacement: {
 | 
				
			||||||
 | 
					                // eslint-disable-next-line no-useless-escape
 | 
				
			||||||
 | 
					                match: /(?<=" platform-overlay"\):)\i/,
 | 
				
			||||||
 | 
					                replace: "$self.getPlatformClass()"
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    getPlatformClass() {
 | 
				
			||||||
 | 
					        if (Settings.store.customTitleBar) return "platform-win";
 | 
				
			||||||
 | 
					        if (isMac) return "platform-osx";
 | 
				
			||||||
 | 
					        return "platform-web";
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										71
									
								
								src/renderer/patches/screenShareFixes.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,71 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * SPDX-License-Identifier: GPL-3.0
 | 
				
			||||||
 | 
					 * Modified for Aerocord, originally part of Vesktop, a desktop app aiming to give you a snappier Discord Experience
 | 
				
			||||||
 | 
					 * Copyright (c) 2024-2024 Aiek 
 | 
				
			||||||
 | 
					 * Copyright (c) 2024 RandomServer Community
 | 
				
			||||||
 | 
					 * Copyright (c) 2023 Vendicated and Vencord contributors
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { Logger } from "@vencord/types/utils";
 | 
				
			||||||
 | 
					import { currentSettings } from "renderer/components/ScreenSharePicker";
 | 
				
			||||||
 | 
					import { isLinux } from "renderer/utils";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const logger = new Logger("VesktopStreamFixes");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if (isLinux) {
 | 
				
			||||||
 | 
					    const original = navigator.mediaDevices.getDisplayMedia;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async function getVirtmic() {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            const devices = await navigator.mediaDevices.enumerateDevices();
 | 
				
			||||||
 | 
					            const audioDevice = devices.find(({ label }) => label === "vencord-screen-share");
 | 
				
			||||||
 | 
					            return audioDevice?.deviceId;
 | 
				
			||||||
 | 
					        } catch (error) {
 | 
				
			||||||
 | 
					            return null;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    navigator.mediaDevices.getDisplayMedia = async function (opts) {
 | 
				
			||||||
 | 
					        const stream = await original.call(this, opts);
 | 
				
			||||||
 | 
					        const id = await getVirtmic();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const frameRate = Number(currentSettings?.fps);
 | 
				
			||||||
 | 
					        const height = Number(currentSettings?.resolution);
 | 
				
			||||||
 | 
					        const width = Math.round(height * (16 / 9));
 | 
				
			||||||
 | 
					        const track = stream.getVideoTracks()[0];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        track.contentHint = String(currentSettings?.contentHint);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const constraints = {
 | 
				
			||||||
 | 
					            ...track.getConstraints(),
 | 
				
			||||||
 | 
					            frameRate: { min: frameRate, ideal: frameRate },
 | 
				
			||||||
 | 
					            width: { min: 640, ideal: width, max: width },
 | 
				
			||||||
 | 
					            height: { min: 480, ideal: height, max: height },
 | 
				
			||||||
 | 
					            advanced: [{ width: width, height: height }],
 | 
				
			||||||
 | 
					            resizeMode: "none"
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        track
 | 
				
			||||||
 | 
					            .applyConstraints(constraints)
 | 
				
			||||||
 | 
					            .then(() => {
 | 
				
			||||||
 | 
					                logger.info("Applied constraints successfully. New constraints: ", track.getConstraints());
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					            .catch(e => logger.error("Failed to apply constraints.", e));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (id) {
 | 
				
			||||||
 | 
					            const audio = await navigator.mediaDevices.getUserMedia({
 | 
				
			||||||
 | 
					                audio: {
 | 
				
			||||||
 | 
					                    deviceId: {
 | 
				
			||||||
 | 
					                        exact: id
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    autoGainControl: false,
 | 
				
			||||||
 | 
					                    echoCancellation: false,
 | 
				
			||||||
 | 
					                    noiseSuppression: false
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					            audio.getAudioTracks().forEach(t => stream.addTrack(t));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return stream;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										32
									
								
								src/renderer/patches/shared.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,32 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * SPDX-License-Identifier: GPL-3.0
 | 
				
			||||||
 | 
					 * Modified for Aerocord, originally part of Vesktop, a desktop app aiming to give you a snappier Discord Experience
 | 
				
			||||||
 | 
					 * Copyright (c) 2024-2024 Aiek 
 | 
				
			||||||
 | 
					 * Copyright (c) 2024 RandomServer Community
 | 
				
			||||||
 | 
					 * Copyright (c) 2023 Vendicated and Vencord contributors
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { Patch } from "@vencord/types/utils/types";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					window.VCDP = {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface PatchData {
 | 
				
			||||||
 | 
					    patches: Omit<Patch, "plugin">[];
 | 
				
			||||||
 | 
					    [key: string]: any;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function addPatch<P extends PatchData>(p: P) {
 | 
				
			||||||
 | 
					    const { patches, ...globals } = p;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (const patch of patches as Patch[]) {
 | 
				
			||||||
 | 
					        if (!Array.isArray(patch.replacement)) patch.replacement = [patch.replacement];
 | 
				
			||||||
 | 
					        for (const r of patch.replacement) {
 | 
				
			||||||
 | 
					            if (typeof r.replace === "string") r.replace = r.replace.replaceAll("$self", "VCDP");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        patch.plugin = "Vesktop";
 | 
				
			||||||
 | 
					        Vencord.Plugins.patches.push(patch);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Object.assign(VCDP, globals);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										119
									
								
								src/renderer/patches/spellCheck.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,119 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * SPDX-License-Identifier: GPL-3.0
 | 
				
			||||||
 | 
					 * Modified for Aerocord, originally part of Vesktop, a desktop app aiming to give you a snappier Discord Experience
 | 
				
			||||||
 | 
					 * Copyright (c) 2024-2024 Aiek 
 | 
				
			||||||
 | 
					 * Copyright (c) 2024 RandomServer Community
 | 
				
			||||||
 | 
					 * Copyright (c) 2023 Vendicated and Vencord contributors
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { addContextMenuPatch } from "@vencord/types/api/ContextMenu";
 | 
				
			||||||
 | 
					import { findStoreLazy } from "@vencord/types/webpack";
 | 
				
			||||||
 | 
					import { FluxDispatcher, Menu, useMemo, useStateFromStores } from "@vencord/types/webpack/common";
 | 
				
			||||||
 | 
					import { useSettings } from "renderer/settings";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { addPatch } from "./shared";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let word: string;
 | 
				
			||||||
 | 
					let corrections: string[];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const SpellCheckStore = findStoreLazy("SpellcheckStore");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Make spellcheck suggestions work
 | 
				
			||||||
 | 
					addPatch({
 | 
				
			||||||
 | 
					    patches: [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            find: ".enableSpellCheck)",
 | 
				
			||||||
 | 
					            replacement: {
 | 
				
			||||||
 | 
					                // if (isDesktop) { DiscordNative.onSpellcheck(openMenu(props)) } else { e.preventDefault(); openMenu(props) }
 | 
				
			||||||
 | 
					                match: /else (.{1,3})\.preventDefault\(\),(.{1,3}\(.{1,3}\))(?<=:(.{1,3})\.enableSpellCheck\).+?)/,
 | 
				
			||||||
 | 
					                // ... else { $self.onSlateContext(() => openMenu(props)) }
 | 
				
			||||||
 | 
					                replace: "else {$self.onSlateContext($1, $3?.enableSpellCheck, () => $2)}"
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    onSlateContext(e: MouseEvent, hasSpellcheck: boolean | undefined, openMenu: () => void) {
 | 
				
			||||||
 | 
					        if (!hasSpellcheck) {
 | 
				
			||||||
 | 
					            e.preventDefault();
 | 
				
			||||||
 | 
					            openMenu();
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const cb = (w: string, c: string[]) => {
 | 
				
			||||||
 | 
					            VesktopNative.spellcheck.offSpellcheckResult(cb);
 | 
				
			||||||
 | 
					            word = w;
 | 
				
			||||||
 | 
					            corrections = c;
 | 
				
			||||||
 | 
					            openMenu();
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        VesktopNative.spellcheck.onSpellcheckResult(cb);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					addContextMenuPatch("textarea-context", children => {
 | 
				
			||||||
 | 
					    const spellCheckEnabled = useStateFromStores([SpellCheckStore], () => SpellCheckStore.isEnabled());
 | 
				
			||||||
 | 
					    const hasCorrections = Boolean(word && corrections?.length);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const availableLanguages = useMemo(VesktopNative.spellcheck.getAvailableLanguages, []);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const settings = useSettings();
 | 
				
			||||||
 | 
					    const spellCheckLanguages = (settings.spellCheckLanguages ??= [...new Set(navigator.languages)]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const pasteSectionIndex = children.findIndex(c => c?.props?.children?.some(c => c?.props?.id === "paste"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    children.splice(
 | 
				
			||||||
 | 
					        pasteSectionIndex === -1 ? children.length : pasteSectionIndex,
 | 
				
			||||||
 | 
					        0,
 | 
				
			||||||
 | 
					        <Menu.MenuGroup>
 | 
				
			||||||
 | 
					            {hasCorrections && (
 | 
				
			||||||
 | 
					                <>
 | 
				
			||||||
 | 
					                    {corrections.map(c => (
 | 
				
			||||||
 | 
					                        <Menu.MenuItem
 | 
				
			||||||
 | 
					                            id={"vcd-spellcheck-suggestion-" + c}
 | 
				
			||||||
 | 
					                            label={c}
 | 
				
			||||||
 | 
					                            action={() => VesktopNative.spellcheck.replaceMisspelling(c)}
 | 
				
			||||||
 | 
					                        />
 | 
				
			||||||
 | 
					                    ))}
 | 
				
			||||||
 | 
					                    <Menu.MenuSeparator />
 | 
				
			||||||
 | 
					                    <Menu.MenuItem
 | 
				
			||||||
 | 
					                        id="vcd-spellcheck-learn"
 | 
				
			||||||
 | 
					                        label={`Add ${word} to dictionary`}
 | 
				
			||||||
 | 
					                        action={() => VesktopNative.spellcheck.addToDictionary(word)}
 | 
				
			||||||
 | 
					                    />
 | 
				
			||||||
 | 
					                </>
 | 
				
			||||||
 | 
					            )}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <Menu.MenuItem id="vcd-spellcheck-settings" label="Spellcheck Settings">
 | 
				
			||||||
 | 
					                <Menu.MenuCheckboxItem
 | 
				
			||||||
 | 
					                    id="vcd-spellcheck-enabled"
 | 
				
			||||||
 | 
					                    label="Enable Spellcheck"
 | 
				
			||||||
 | 
					                    checked={spellCheckEnabled}
 | 
				
			||||||
 | 
					                    action={() => {
 | 
				
			||||||
 | 
					                        FluxDispatcher.dispatch({ type: "SPELLCHECK_TOGGLE" });
 | 
				
			||||||
 | 
					                    }}
 | 
				
			||||||
 | 
					                />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <Menu.MenuItem id="vcd-spellcheck-languages" label="Languages" disabled={!spellCheckEnabled}>
 | 
				
			||||||
 | 
					                    {availableLanguages.map(lang => {
 | 
				
			||||||
 | 
					                        const isEnabled = spellCheckLanguages.includes(lang);
 | 
				
			||||||
 | 
					                        return (
 | 
				
			||||||
 | 
					                            <Menu.MenuCheckboxItem
 | 
				
			||||||
 | 
					                                id={"vcd-spellcheck-lang-" + lang}
 | 
				
			||||||
 | 
					                                label={lang}
 | 
				
			||||||
 | 
					                                checked={isEnabled}
 | 
				
			||||||
 | 
					                                disabled={!isEnabled && spellCheckLanguages.length >= 5}
 | 
				
			||||||
 | 
					                                action={() => {
 | 
				
			||||||
 | 
					                                    const newSpellCheckLanguages = spellCheckLanguages.filter(l => l !== lang);
 | 
				
			||||||
 | 
					                                    if (newSpellCheckLanguages.length === spellCheckLanguages.length) {
 | 
				
			||||||
 | 
					                                        newSpellCheckLanguages.push(lang);
 | 
				
			||||||
 | 
					                                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                                    settings.spellCheckLanguages = newSpellCheckLanguages;
 | 
				
			||||||
 | 
					                                }}
 | 
				
			||||||
 | 
					                            />
 | 
				
			||||||
 | 
					                        );
 | 
				
			||||||
 | 
					                    })}
 | 
				
			||||||
 | 
					                </Menu.MenuItem>
 | 
				
			||||||
 | 
					            </Menu.MenuItem>
 | 
				
			||||||
 | 
					        </Menu.MenuGroup>
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										32
									
								
								src/renderer/patches/windowsTitleBar.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,32 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * SPDX-License-Identifier: GPL-3.0
 | 
				
			||||||
 | 
					 * Modified for Aerocord, originally part of Vesktop, a desktop app aiming to give you a snappier Discord Experience
 | 
				
			||||||
 | 
					 * Copyright (c) 2024-2024 Aiek 
 | 
				
			||||||
 | 
					 * Copyright (c) 2024 RandomServer Community
 | 
				
			||||||
 | 
					 * Copyright (c) 2023 Vendicated and Vencord contributors
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { Settings } from "renderer/settings";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { addPatch } from "./shared";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if (Settings.store.customTitleBar)
 | 
				
			||||||
 | 
					    addPatch({
 | 
				
			||||||
 | 
					        patches: [
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                find: ".wordmarkWindows",
 | 
				
			||||||
 | 
					                replacement: [
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        // TODO: Fix eslint rule
 | 
				
			||||||
 | 
					                        // eslint-disable-next-line no-useless-escape
 | 
				
			||||||
 | 
					                        match: /case \i\.\i\.WINDOWS:/,
 | 
				
			||||||
 | 
					                        replace: 'case "WEB":'
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    ...["close", "minimize", "maximize"].map(op => ({
 | 
				
			||||||
 | 
					                        match: new RegExp(String.raw`\i\.\i\.${op}\b`),
 | 
				
			||||||
 | 
					                        replace: `VesktopNative.win.${op}`
 | 
				
			||||||
 | 
					                    }))
 | 
				
			||||||
 | 
					                ]
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
							
								
								
									
										32
									
								
								src/renderer/settings.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,32 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * SPDX-License-Identifier: GPL-3.0
 | 
				
			||||||
 | 
					 * Modified for Aerocord, originally part of Vesktop, a desktop app aiming to give you a snappier Discord Experience
 | 
				
			||||||
 | 
					 * Copyright (c) 2024-2024 Aiek 
 | 
				
			||||||
 | 
					 * Copyright (c) 2024 RandomServer Community
 | 
				
			||||||
 | 
					 * Copyright (c) 2023 Vendicated and Vencord contributors
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { useEffect, useReducer } from "@vencord/types/webpack/common";
 | 
				
			||||||
 | 
					import { SettingsStore } from "shared/utils/SettingsStore";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const Settings = new SettingsStore(VesktopNative.settings.get());
 | 
				
			||||||
 | 
					Settings.addGlobalChangeListener((o, p) => VesktopNative.settings.set(o, p));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function useSettings() {
 | 
				
			||||||
 | 
					    const [, update] = useReducer(x => x + 1, 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    useEffect(() => {
 | 
				
			||||||
 | 
					        Settings.addGlobalChangeListener(update);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return () => Settings.removeGlobalChangeListener(update);
 | 
				
			||||||
 | 
					    }, []);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return Settings.store;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function getValueAndOnChange(key: keyof typeof Settings.store) {
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					        value: Settings.store[key] as any,
 | 
				
			||||||
 | 
					        onChange: (value: any) => (Settings.store[key] = value)
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										48
									
								
								src/renderer/themedSplash.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,48 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * SPDX-License-Identifier: GPL-3.0
 | 
				
			||||||
 | 
					 * Modified for Aerocord, originally part of Vesktop, a desktop app aiming to give you a snappier Discord Experience
 | 
				
			||||||
 | 
					 * Copyright (c) 2024-2024 Aiek 
 | 
				
			||||||
 | 
					 * Copyright (c) 2024 RandomServer Community
 | 
				
			||||||
 | 
					 * Copyright (c) 2023 Vendicated and Vencord contributors
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { Settings } from "./settings";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function isValidColor(color: CSSStyleValue | undefined): color is CSSUnparsedValue & { [0]: string } {
 | 
				
			||||||
 | 
					    return color instanceof CSSUnparsedValue && typeof color[0] === "string" && CSS.supports("color", color[0]);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function resolveColor(color: string) {
 | 
				
			||||||
 | 
					    const span = document.createElement("span");
 | 
				
			||||||
 | 
					    span.style.color = color;
 | 
				
			||||||
 | 
					    span.style.display = "none";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    document.body.append(span);
 | 
				
			||||||
 | 
					    const rgbColor = getComputedStyle(span).color;
 | 
				
			||||||
 | 
					    span.remove();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return rgbColor;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const updateSplashColors = () => {
 | 
				
			||||||
 | 
					    const bodyStyles = document.body.computedStyleMap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const color = bodyStyles.get("--text-normal");
 | 
				
			||||||
 | 
					    const backgroundColor = bodyStyles.get("--background-primary");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (isValidColor(color)) {
 | 
				
			||||||
 | 
					        Settings.store.splashColor = resolveColor(color[0]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (isValidColor(backgroundColor)) {
 | 
				
			||||||
 | 
					        Settings.store.splashBackground = resolveColor(backgroundColor[0]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if (document.readyState === "complete") {
 | 
				
			||||||
 | 
					    updateSplashColors();
 | 
				
			||||||
 | 
					} else {
 | 
				
			||||||
 | 
					    window.addEventListener("load", updateSplashColors);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					window.addEventListener("beforeunload", updateSplashColors);
 | 
				
			||||||
							
								
								
									
										22
									
								
								src/renderer/utils.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,22 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * SPDX-License-Identifier: GPL-3.0
 | 
				
			||||||
 | 
					 * Modified for Aerocord, originally part of Vesktop, a desktop app aiming to give you a snappier Discord Experience
 | 
				
			||||||
 | 
					 * Copyright (c) 2024-2024 Aiek 
 | 
				
			||||||
 | 
					 * Copyright (c) 2024 RandomServer Community
 | 
				
			||||||
 | 
					 * Copyright (c) 2023 Vendicated and Vencord contributors
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const { localStorage } = window;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const isFirstRun = (() => {
 | 
				
			||||||
 | 
					    const key = "VCD_FIRST_RUN";
 | 
				
			||||||
 | 
					    if (localStorage.getItem(key) !== null) return false;
 | 
				
			||||||
 | 
					    localStorage.setItem(key, "false");
 | 
				
			||||||
 | 
					    return true;
 | 
				
			||||||
 | 
					})();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const { platform } = navigator;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const isWindows = platform.startsWith("Win");
 | 
				
			||||||
 | 
					export const isMac = platform.startsWith("Mac");
 | 
				
			||||||
 | 
					export const isLinux = platform.startsWith("Linux");
 | 
				
			||||||
							
								
								
									
										56
									
								
								src/shared/IpcEvents.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,56 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * SPDX-License-Identifier: GPL-3.0
 | 
				
			||||||
 | 
					 * Modified for Aerocord, originally part of Vesktop, a desktop app aiming to give you a snappier Discord Experience
 | 
				
			||||||
 | 
					 * Copyright (c) 2024-2024 Aiek 
 | 
				
			||||||
 | 
					 * Copyright (c) 2024 RandomServer Community
 | 
				
			||||||
 | 
					 * Copyright (c) 2023 Vendicated and Vencord contributors
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const enum IpcEvents {
 | 
				
			||||||
 | 
					    GET_VENCORD_PRELOAD_FILE = "VCD_GET_VC_PRELOAD_FILE",
 | 
				
			||||||
 | 
					    GET_VENCORD_RENDERER_SCRIPT = "VCD_GET_VC_RENDERER_SCRIPT",
 | 
				
			||||||
 | 
					    GET_RENDERER_SCRIPT = "VCD_GET_RENDERER_SCRIPT",
 | 
				
			||||||
 | 
					    GET_RENDERER_CSS_FILE = "VCD_GET_RENDERER_CSS_FILE",
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    GET_VERSION = "VCD_GET_VERSION",
 | 
				
			||||||
 | 
					    SUPPORTS_WINDOWS_TRANSPARENCY = "VCD_SUPPORTS_WINDOWS_TRANSPARENCY",
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    RELAUNCH = "VCD_RELAUNCH",
 | 
				
			||||||
 | 
					    CLOSE = "VCD_CLOSE",
 | 
				
			||||||
 | 
					    FOCUS = "VCD_FOCUS",
 | 
				
			||||||
 | 
					    MINIMIZE = "VCD_MINIMIZE",
 | 
				
			||||||
 | 
					    MAXIMIZE = "VCD_MAXIMIZE",
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    SHOW_ITEM_IN_FOLDER = "VCD_SHOW_ITEM_IN_FOLDER",
 | 
				
			||||||
 | 
					    GET_SETTINGS = "VCD_GET_SETTINGS",
 | 
				
			||||||
 | 
					    SET_SETTINGS = "VCD_SET_SETTINGS",
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    GET_VENCORD_DIR = "VCD_GET_VENCORD_DIR",
 | 
				
			||||||
 | 
					    SELECT_VENCORD_DIR = "VCD_SELECT_VENCORD_DIR",
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    UPDATER_GET_DATA = "VCD_UPDATER_GET_DATA",
 | 
				
			||||||
 | 
					    UPDATER_DOWNLOAD = "VCD_UPDATER_DOWNLOAD",
 | 
				
			||||||
 | 
					    UPDATE_IGNORE = "VCD_UPDATE_IGNORE",
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    SPELLCHECK_GET_AVAILABLE_LANGUAGES = "VCD_SPELLCHECK_GET_AVAILABLE_LANGUAGES",
 | 
				
			||||||
 | 
					    SPELLCHECK_RESULT = "VCD_SPELLCHECK_RESULT",
 | 
				
			||||||
 | 
					    SPELLCHECK_REPLACE_MISSPELLING = "VCD_SPELLCHECK_REPLACE_MISSPELLING",
 | 
				
			||||||
 | 
					    SPELLCHECK_ADD_TO_DICTIONARY = "VCD_SPELLCHECK_ADD_TO_DICTIONARY",
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    SET_BADGE_COUNT = "VCD_SET_BADGE_COUNT",
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    CAPTURER_GET_LARGE_THUMBNAIL = "VCD_CAPTURER_GET_LARGE_THUMBNAIL",
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    AUTOSTART_ENABLED = "VCD_AUTOSTART_ENABLED",
 | 
				
			||||||
 | 
					    ENABLE_AUTOSTART = "VCD_ENABLE_AUTOSTART",
 | 
				
			||||||
 | 
					    DISABLE_AUTOSTART = "VCD_DISABLE_AUTOSTART",
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    VIRT_MIC_LIST = "VCD_VIRT_MIC_LIST",
 | 
				
			||||||
 | 
					    VIRT_MIC_START = "VCD_VIRT_MIC_START",
 | 
				
			||||||
 | 
					    VIRT_MIC_START_SYSTEM = "VCD_VIRT_MIC_START_ALL",
 | 
				
			||||||
 | 
					    VIRT_MIC_STOP = "VCD_VIRT_MIC_STOP",
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ARRPC_ACTIVITY = "VCD_ARRPC_ACTIVITY",
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    CLIPBOARD_COPY_IMAGE = "VCD_CLIPBOARD_COPY_IMAGE"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										20
									
								
								src/shared/browserWinProperties.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,20 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * SPDX-License-Identifier: GPL-3.0
 | 
				
			||||||
 | 
					 * Modified for Aerocord, originally part of Vesktop, a desktop app aiming to give you a snappier Discord Experience
 | 
				
			||||||
 | 
					 * Copyright (c) 2024-2024 Aiek 
 | 
				
			||||||
 | 
					 * Copyright (c) 2024 RandomServer Community
 | 
				
			||||||
 | 
					 * Copyright (c) 2023 Vendicated and Vencord contributors
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import type { BrowserWindowConstructorOptions } from "electron";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const SplashProps: BrowserWindowConstructorOptions = {
 | 
				
			||||||
 | 
					    transparent: true,
 | 
				
			||||||
 | 
					    frame: false,
 | 
				
			||||||
 | 
					    height: 350,
 | 
				
			||||||
 | 
					    width: 300,
 | 
				
			||||||
 | 
					    center: true,
 | 
				
			||||||
 | 
					    resizable: false,
 | 
				
			||||||
 | 
					    maximizable: false,
 | 
				
			||||||
 | 
					    alwaysOnTop: true
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										14
									
								
								src/shared/paths.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,14 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * SPDX-License-Identifier: GPL-3.0
 | 
				
			||||||
 | 
					 * Modified for Aerocord, originally part of Vesktop, a desktop app aiming to give you a snappier Discord Experience
 | 
				
			||||||
 | 
					 * Copyright (c) 2024-2024 Aiek 
 | 
				
			||||||
 | 
					 * Copyright (c) 2024 RandomServer Community
 | 
				
			||||||
 | 
					 * Copyright (c) 2023 Vendicated and Vencord contributors
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { join } from "path";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const STATIC_DIR = /* @__PURE__ */ join(__dirname, "..", "..", "static");
 | 
				
			||||||
 | 
					export const VIEW_DIR = /* @__PURE__ */ join(STATIC_DIR, "views");
 | 
				
			||||||
 | 
					export const BADGE_DIR = /* @__PURE__ */ join(STATIC_DIR, "badges");
 | 
				
			||||||
 | 
					export const ICON_PATH = /* @__PURE__ */ join(STATIC_DIR, "icon.png");
 | 
				
			||||||
							
								
								
									
										59
									
								
								src/shared/settings.d.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,59 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * SPDX-License-Identifier: GPL-3.0
 | 
				
			||||||
 | 
					 * Modified for Aerocord, originally part of Vesktop, a desktop app aiming to give you a snappier Discord Experience
 | 
				
			||||||
 | 
					 * Copyright (c) 2024-2024 Aiek 
 | 
				
			||||||
 | 
					 * Copyright (c) 2024 RandomServer Community
 | 
				
			||||||
 | 
					 * Copyright (c) 2023 Vendicated and Vencord contributors
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import type { Rectangle } from "electron";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface Settings {
 | 
				
			||||||
 | 
					    discordBranch?: "stable" | "canary" | "ptb";
 | 
				
			||||||
 | 
					    transparencyOption?: "none" | "mica" | "tabbed" | "acrylic";
 | 
				
			||||||
 | 
					    tray?: boolean;
 | 
				
			||||||
 | 
					    minimizeToTray?: boolean;
 | 
				
			||||||
 | 
					    openLinksWithElectron?: boolean;
 | 
				
			||||||
 | 
					    staticTitle?: boolean;
 | 
				
			||||||
 | 
					    enableMenu?: boolean;
 | 
				
			||||||
 | 
					    disableSmoothScroll?: boolean;
 | 
				
			||||||
 | 
					    hardwareAcceleration?: boolean;
 | 
				
			||||||
 | 
					    arRPC?: boolean;
 | 
				
			||||||
 | 
					    appBadge?: boolean;
 | 
				
			||||||
 | 
					    disableMinSize?: boolean;
 | 
				
			||||||
 | 
					    clickTrayToShowHide?: boolean;
 | 
				
			||||||
 | 
					    customTitleBar?: boolean;
 | 
				
			||||||
 | 
					    checkUpdates?: boolean;
 | 
				
			||||||
 | 
					    splashTheming?: boolean;
 | 
				
			||||||
 | 
					    splashColor?: string;
 | 
				
			||||||
 | 
					    splashBackground?: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    spellCheckLanguages?: string[];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    audio?: {
 | 
				
			||||||
 | 
					        workaround?: boolean;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        deviceSelect?: boolean;
 | 
				
			||||||
 | 
					        granularSelect?: boolean;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ignoreVirtual?: boolean;
 | 
				
			||||||
 | 
					        ignoreDevices?: boolean;
 | 
				
			||||||
 | 
					        ignoreInputMedia?: boolean;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        onlySpeakers?: boolean;
 | 
				
			||||||
 | 
					        onlyDefaultSpeakers?: boolean;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface State {
 | 
				
			||||||
 | 
					    maximized?: boolean;
 | 
				
			||||||
 | 
					    minimized?: boolean;
 | 
				
			||||||
 | 
					    windowBounds?: Rectangle;
 | 
				
			||||||
 | 
					    displayid: int;
 | 
				
			||||||
 | 
					    skippedUpdate?: string;
 | 
				
			||||||
 | 
					    firstLaunch?: boolean;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    steamOSLayoutVersion?: number;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    vencordDir?: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										169
									
								
								src/shared/utils/SettingsStore.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,169 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * SPDX-License-Identifier: GPL-3.0
 | 
				
			||||||
 | 
					 * Modified for Aerocord, originally part of Vesktop, a desktop app aiming to give you a snappier Discord Experience
 | 
				
			||||||
 | 
					 * Copyright (c) 2024-2024 Aiek 
 | 
				
			||||||
 | 
					 * Copyright (c) 2024 RandomServer Community
 | 
				
			||||||
 | 
					 * Copyright (c) 2023 Vendicated and Vencord contributors
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { LiteralUnion } from "type-fest";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Resolves a possibly nested prop in the form of "some.nested.prop" to type of T.some.nested.prop
 | 
				
			||||||
 | 
					type ResolvePropDeep<T, P> = P extends `${infer Pre}.${infer Suf}`
 | 
				
			||||||
 | 
					    ? Pre extends keyof T
 | 
				
			||||||
 | 
					        ? ResolvePropDeep<T[Pre], Suf>
 | 
				
			||||||
 | 
					        : any
 | 
				
			||||||
 | 
					    : P extends keyof T
 | 
				
			||||||
 | 
					      ? T[P]
 | 
				
			||||||
 | 
					      : any;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * The SettingsStore allows you to easily create a mutable store that
 | 
				
			||||||
 | 
					 * has support for global and path-based change listeners.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export class SettingsStore<T extends object> {
 | 
				
			||||||
 | 
					    private pathListeners = new Map<string, Set<(newData: any) => void>>();
 | 
				
			||||||
 | 
					    private globalListeners = new Set<(newData: T, path: string) => void>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * The store object. Making changes to this object will trigger the applicable change listeners
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    declare public store: T;
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * The plain data. Changes to this object will not trigger any change listeners
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    declare public plain: T;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public constructor(plain: T) {
 | 
				
			||||||
 | 
					        this.plain = plain;
 | 
				
			||||||
 | 
					        this.store = this.makeProxy(plain);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private makeProxy(object: any, root: T = object, path: string = "") {
 | 
				
			||||||
 | 
					        const self = this;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return new Proxy(object, {
 | 
				
			||||||
 | 
					            get(target, key: string) {
 | 
				
			||||||
 | 
					                const v = target[key];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (typeof v === "object" && v !== null && !Array.isArray(v))
 | 
				
			||||||
 | 
					                    return self.makeProxy(v, root, `${path}${path && "."}${key}`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                return v;
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            set(target, key: string, value) {
 | 
				
			||||||
 | 
					                if (target[key] === value) return true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                Reflect.set(target, key, value);
 | 
				
			||||||
 | 
					                const setPath = `${path}${path && "."}${key}`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                self.globalListeners.forEach(cb => cb(root, setPath));
 | 
				
			||||||
 | 
					                self.pathListeners.get(setPath)?.forEach(cb => cb(value));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                return true;
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            deleteProperty(target, key: string) {
 | 
				
			||||||
 | 
					                if (!(key in target)) return true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                const res = Reflect.deleteProperty(target, key);
 | 
				
			||||||
 | 
					                if (!res) return false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                const setPath = `${path}${path && "."}${key}`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                self.globalListeners.forEach(cb => cb(root, setPath));
 | 
				
			||||||
 | 
					                self.pathListeners.get(setPath)?.forEach(cb => cb(undefined));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                return res;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Set the data of the store.
 | 
				
			||||||
 | 
					     * This will update this.store and this.plain (and old references to them will be stale! Avoid storing them in variables)
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * Additionally, all global listeners (and those for pathToNotify, if specified) will be called with the new data
 | 
				
			||||||
 | 
					     * @param value New data
 | 
				
			||||||
 | 
					     * @param pathToNotify Optional path to notify instead of globally. Used to transfer path via ipc
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public setData(value: T, pathToNotify?: string) {
 | 
				
			||||||
 | 
					        this.plain = value;
 | 
				
			||||||
 | 
					        this.store = this.makeProxy(value);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (pathToNotify) {
 | 
				
			||||||
 | 
					            let v = value;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            const path = pathToNotify.split(".");
 | 
				
			||||||
 | 
					            for (const p of path) {
 | 
				
			||||||
 | 
					                if (!v) {
 | 
				
			||||||
 | 
					                    console.warn(
 | 
				
			||||||
 | 
					                        `Settings#setData: Path ${pathToNotify} does not exist in new data. Not dispatching update`
 | 
				
			||||||
 | 
					                    );
 | 
				
			||||||
 | 
					                    return;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                v = v[p];
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            this.pathListeners.get(pathToNotify)?.forEach(cb => cb(v));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.globalListeners.forEach(cb => cb(value, ""));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Add a global change listener, that will fire whenever any setting is changed
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public addGlobalChangeListener(cb: (data: T, path: string) => void) {
 | 
				
			||||||
 | 
					        this.globalListeners.add(cb);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Add a scoped change listener that will fire whenever a setting matching the specified path is changed.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * For example if path is `"foo.bar"`, the listener will fire on
 | 
				
			||||||
 | 
					     * ```js
 | 
				
			||||||
 | 
					     * Setting.store.foo.bar = "hi"
 | 
				
			||||||
 | 
					     * ```
 | 
				
			||||||
 | 
					     * but not on
 | 
				
			||||||
 | 
					     * ```js
 | 
				
			||||||
 | 
					     * Setting.store.foo.baz = "hi"
 | 
				
			||||||
 | 
					     * ```
 | 
				
			||||||
 | 
					     * @param path
 | 
				
			||||||
 | 
					     * @param cb
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public addChangeListener<P extends LiteralUnion<keyof T, string>>(
 | 
				
			||||||
 | 
					        path: P,
 | 
				
			||||||
 | 
					        cb: (data: ResolvePropDeep<T, P>) => void
 | 
				
			||||||
 | 
					    ) {
 | 
				
			||||||
 | 
					        const listeners = this.pathListeners.get(path as string) ?? new Set();
 | 
				
			||||||
 | 
					        listeners.add(cb);
 | 
				
			||||||
 | 
					        this.pathListeners.set(path as string, listeners);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Remove a global listener
 | 
				
			||||||
 | 
					     * @see {@link addGlobalChangeListener}
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public removeGlobalChangeListener(cb: (data: T, path: string) => void) {
 | 
				
			||||||
 | 
					        this.globalListeners.delete(cb);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Remove a scoped listener
 | 
				
			||||||
 | 
					     * @see {@link addChangeListener}
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public removeChangeListener(path: LiteralUnion<keyof T, string>, cb: (data: any) => void) {
 | 
				
			||||||
 | 
					        const listeners = this.pathListeners.get(path as string);
 | 
				
			||||||
 | 
					        if (!listeners) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        listeners.delete(cb);
 | 
				
			||||||
 | 
					        if (!listeners.size) this.pathListeners.delete(path as string);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Call all global change listeners
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public markAsChanged() {
 | 
				
			||||||
 | 
					        this.globalListeners.forEach(cb => cb(this.plain, ""));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										23
									
								
								src/shared/utils/debounce.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,23 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * SPDX-License-Identifier: GPL-3.0
 | 
				
			||||||
 | 
					 * Modified for Aerocord, originally part of Vesktop, a desktop app aiming to give you a snappier Discord Experience
 | 
				
			||||||
 | 
					 * Copyright (c) 2024-2024 Aiek 
 | 
				
			||||||
 | 
					 * Copyright (c) 2024 RandomServer Community
 | 
				
			||||||
 | 
					 * Copyright (c) 2023 Vendicated and Vencord contributors
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Returns a new function that will only be called after the given delay.
 | 
				
			||||||
 | 
					 * Subsequent calls will cancel the previous timeout and start a new one from 0
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Useful for grouping multiple calls into one
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export function debounce<T extends Function>(func: T, delay = 300): T {
 | 
				
			||||||
 | 
					    let timeout: NodeJS.Timeout;
 | 
				
			||||||
 | 
					    return function (...args: any[]) {
 | 
				
			||||||
 | 
					        clearTimeout(timeout);
 | 
				
			||||||
 | 
					        timeout = setTimeout(() => {
 | 
				
			||||||
 | 
					            func(...args);
 | 
				
			||||||
 | 
					        }, delay);
 | 
				
			||||||
 | 
					    } as any;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										15
									
								
								src/shared/utils/guards.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,15 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * SPDX-License-Identifier: GPL-3.0
 | 
				
			||||||
 | 
					 * Modified for Aerocord, originally part of Vesktop, a desktop app aiming to give you a snappier Discord Experience
 | 
				
			||||||
 | 
					 * Copyright (c) 2024-2024 Aiek 
 | 
				
			||||||
 | 
					 * Copyright (c) 2024 RandomServer Community
 | 
				
			||||||
 | 
					 * Copyright (c) 2023 Vendicated and Vencord contributors
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function isTruthy<T>(item: T): item is Exclude<T, 0 | "" | false | null | undefined> {
 | 
				
			||||||
 | 
					    return Boolean(item);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function isNonNullish<T>(item: T): item is Exclude<T, null | undefined> {
 | 
				
			||||||
 | 
					    return item != null;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										21
									
								
								src/shared/utils/once.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,21 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * SPDX-License-Identifier: GPL-3.0
 | 
				
			||||||
 | 
					 * Modified for Aerocord, originally part of Vesktop, a desktop app aiming to give you a snappier Discord Experience
 | 
				
			||||||
 | 
					 * Copyright (c) 2024-2024 Aiek 
 | 
				
			||||||
 | 
					 * Copyright (c) 2024 RandomServer Community
 | 
				
			||||||
 | 
					 * Copyright (c) 2023 Vendicated and Vencord contributors
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Wraps the given function so that it can only be called once
 | 
				
			||||||
 | 
					 * @param fn Function to wrap
 | 
				
			||||||
 | 
					 * @returns New function that can only be called once
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					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;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										11
									
								
								src/shared/utils/sleep.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,11 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * SPDX-License-Identifier: GPL-3.0
 | 
				
			||||||
 | 
					 * Modified for Aerocord, originally part of Vesktop, a desktop app aiming to give you a snappier Discord Experience
 | 
				
			||||||
 | 
					 * Copyright (c) 2024-2024 Aiek 
 | 
				
			||||||
 | 
					 * Copyright (c) 2024 RandomServer Community
 | 
				
			||||||
 | 
					 * Copyright (c) 2023 Vendicated and Vencord contributors
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function sleep(ms: number): Promise<void> {
 | 
				
			||||||
 | 
					    return new Promise(r => setTimeout(r, ms));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										97
									
								
								src/updater/main.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,97 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * SPDX-License-Identifier: GPL-3.0
 | 
				
			||||||
 | 
					 * Aerocord, a vesktop fork for older microsoft NT releases such as NT 6.0, 6.1, 6.2 and 6.3. 
 | 
				
			||||||
 | 
					 * Credits to vendicated and the rest of the vesktop contribuitors for making Vesktop!
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// lets just use our own c# updater cuz why not
 | 
				
			||||||
 | 
					import { app, BrowserWindow, shell } from "electron";
 | 
				
			||||||
 | 
					import { Settings, State } from "main/settings";
 | 
				
			||||||
 | 
					import { handle } from "main/utils/ipcWrappers";
 | 
				
			||||||
 | 
					import { makeLinksOpenExternally } from "main/utils/makeLinksOpenExternally";
 | 
				
			||||||
 | 
					import { githubGet, ReleaseData } from "main/utils/vencordLoader";
 | 
				
			||||||
 | 
					import { join } from "path";
 | 
				
			||||||
 | 
					import { IpcEvents } from "shared/IpcEvents";
 | 
				
			||||||
 | 
					import { ICON_PATH, VIEW_DIR } from "shared/paths";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface UpdateData {
 | 
				
			||||||
 | 
					    currentVersion: string;
 | 
				
			||||||
 | 
					    latestVersion: string;
 | 
				
			||||||
 | 
					    release: ReleaseData;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let updateData: UpdateData;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					handle(IpcEvents.UPDATER_GET_DATA, () => updateData);
 | 
				
			||||||
 | 
					handle(IpcEvents.UPDATER_DOWNLOAD, () => {
 | 
				
			||||||
 | 
					    const updaterPath = join(app.getPath('exe'), '..', 'Updater.exe')
 | 
				
			||||||
 | 
					    shell.openPath(updaterPath);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					handle(IpcEvents.UPDATE_IGNORE, () => {
 | 
				
			||||||
 | 
					    State.store.skippedUpdate = updateData.latestVersion;
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function isOutdated(oldVersion: string, newVersion: string) {
 | 
				
			||||||
 | 
					    const oldParts = oldVersion.split(".");
 | 
				
			||||||
 | 
					    const newParts = newVersion.split(".");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (oldParts.length !== newParts.length)
 | 
				
			||||||
 | 
					        throw new Error(`Incompatible version strings (old: ${oldVersion}, new: ${newVersion})`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (let i = 0; i < oldParts.length; i++) {
 | 
				
			||||||
 | 
					        const oldPart = Number(oldParts[i]);
 | 
				
			||||||
 | 
					        const newPart = Number(newParts[i]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (isNaN(oldPart) || isNaN(newPart))
 | 
				
			||||||
 | 
					            throw new Error(`Invalid version string (old: ${oldVersion}, new: ${newVersion})`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (oldPart < newPart) return true;
 | 
				
			||||||
 | 
					        if (oldPart > newPart) return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return false;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export async function checkUpdates() {
 | 
				
			||||||
 | 
					    if (Settings.store.checkUpdates === false) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    try { // make this work with gitea cuz FUK GITHUB!
 | 
				
			||||||
 | 
					        const raw = await fetch("https://git.randomserver.top/api/v1/repos/aiek/aerocord/releases/latest");
 | 
				
			||||||
 | 
					        const data = await raw.json();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const oldVersion = app.getVersion();
 | 
				
			||||||
 | 
					        const newVersion = data.tag_name.replace(/^v/, "");
 | 
				
			||||||
 | 
					        updateData = {
 | 
				
			||||||
 | 
					            currentVersion: oldVersion,
 | 
				
			||||||
 | 
					            latestVersion: newVersion,
 | 
				
			||||||
 | 
					            release: data
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (State.store.skippedUpdate !== newVersion && isOutdated(oldVersion, newVersion)) {
 | 
				
			||||||
 | 
					            openNewUpdateWindow();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					        console.error("AppUpdater: Failed to check for updates\n", e);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function openNewUpdateWindow() {
 | 
				
			||||||
 | 
					    const win = new BrowserWindow({
 | 
				
			||||||
 | 
					        width: 500,
 | 
				
			||||||
 | 
					        autoHideMenuBar: true,
 | 
				
			||||||
 | 
					        alwaysOnTop: true,
 | 
				
			||||||
 | 
					        webPreferences: {
 | 
				
			||||||
 | 
					            preload: join(__dirname, "updaterPreload.js"),
 | 
				
			||||||
 | 
					            nodeIntegration: false,
 | 
				
			||||||
 | 
					            contextIsolation: true,
 | 
				
			||||||
 | 
					            sandbox: true
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        icon: ICON_PATH
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    makeLinksOpenExternally(win);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    win.loadFile(join(VIEW_DIR, "updater.html"));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										21
									
								
								src/updater/preload.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,21 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * SPDX-License-Identifier: GPL-3.0
 | 
				
			||||||
 | 
					 * Aerocord, a vesktop fork for older microsoft NT releases such as NT 6.0, 6.1, 6.2 and 6.3. 
 | 
				
			||||||
 | 
					 * Credits to vendicated and the rest of the vesktop contribuitors for making Vesktop!
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { contextBridge } from "electron";
 | 
				
			||||||
 | 
					import { invoke } from "preload/typedIpc";
 | 
				
			||||||
 | 
					import { IpcEvents } from "shared/IpcEvents";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import type { UpdateData } from "./main";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					contextBridge.exposeInMainWorld("Updater", {
 | 
				
			||||||
 | 
					    getData: () => invoke<UpdateData>(IpcEvents.UPDATER_GET_DATA),
 | 
				
			||||||
 | 
					    download: () => {
 | 
				
			||||||
 | 
					        invoke<void>(IpcEvents.UPDATER_DOWNLOAD);
 | 
				
			||||||
 | 
					        invoke<void>(IpcEvents.CLOSE);
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    ignore: () => invoke<void>(IpcEvents.UPDATE_IGNORE),
 | 
				
			||||||
 | 
					    close: () => invoke<void>(IpcEvents.CLOSE)
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										
											BIN
										
									
								
								static/badges/1.ico
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 15 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								static/badges/10.ico
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 15 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								static/badges/11.ico
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 15 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								static/badges/2.ico
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 15 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								static/badges/3.ico
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 15 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								static/badges/4.ico
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 15 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								static/badges/5.ico
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 15 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								static/badges/6.ico
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 15 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								static/badges/7.ico
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 15 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								static/badges/8.ico
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 15 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								static/badges/9.ico
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 15 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								static/icon.ico
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 201 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								static/icon.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 68 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								static/shiggy.gif
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 1.2 MiB  | 
							
								
								
									
										56
									
								
								static/views/about.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,56 @@
 | 
				
			|||||||
 | 
					<head>
 | 
				
			||||||
 | 
					    <link rel="stylesheet" href="./style.css" type="text/css" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <style>
 | 
				
			||||||
 | 
					        body {
 | 
				
			||||||
 | 
					            padding: 2em;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        h1 {
 | 
				
			||||||
 | 
					            text-align: center;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    </style>
 | 
				
			||||||
 | 
					</head>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<body>
 | 
				
			||||||
 | 
					    <h1 id="title">Aerocord</h1>
 | 
				
			||||||
 | 
					    <p>
 | 
				
			||||||
 | 
					        Aerocord is a Vesktop fork made to work with Windows Vista/7/8.
 | 
				
			||||||
 | 
					    </p>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <section>
 | 
				
			||||||
 | 
					        <h2>Aerocord Gitea:</h2>
 | 
				
			||||||
 | 
					        <ul>
 | 
				
			||||||
 | 
					            <li>
 | 
				
			||||||
 | 
					                <a href="https://git.randomserver.top/administrator/aerocord" target="_blank">Aerocord Source Code</a>
 | 
				
			||||||
 | 
					            </li>
 | 
				
			||||||
 | 
					        </ul>
 | 
				
			||||||
 | 
					    </section>
 | 
				
			||||||
 | 
					    <section>
 | 
				
			||||||
 | 
					        <h2>Credits to the original Vesktop developer:</h2>
 | 
				
			||||||
 | 
					        <ul>
 | 
				
			||||||
 | 
					            <li>
 | 
				
			||||||
 | 
					                <a href="https://vencord.dev" target="_blank">Vencord Website</a>
 | 
				
			||||||
 | 
					            </li>
 | 
				
			||||||
 | 
					            <li>
 | 
				
			||||||
 | 
					                <a href="https://github.com/Vencord/Vesktop" target="_blank">Vesktop Source Code</a>
 | 
				
			||||||
 | 
					            </li>
 | 
				
			||||||
 | 
					        </ul>
 | 
				
			||||||
 | 
					    </section>
 | 
				
			||||||
 | 
					    <div>
 | 
				
			||||||
 | 
					        <a href="https://git.randomserver.top/administrator/aerocord/src/branch/main/documentation.md"
 | 
				
			||||||
 | 
					            target="_blank">Documentation can be found here</a>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					    <div>
 | 
				
			||||||
 | 
					        <a href="https://git.randomserver.top/administrator/aerocord/src/branch/main/build.md" target="_blank">Building
 | 
				
			||||||
 | 
					            instructions can be found here</a>
 | 
				
			||||||
 | 
					</body>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script type="module">
 | 
				
			||||||
 | 
					    const data = await Updater.getData();
 | 
				
			||||||
 | 
					    if (data.currentVersion) {
 | 
				
			||||||
 | 
					        const title = document.getElementById("title");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        title.textContent += ` v${data.currentVersion}`;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
							
								
								
									
										168
									
								
								static/views/first-launch.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,168 @@
 | 
				
			|||||||
 | 
					<head>
 | 
				
			||||||
 | 
					    <link rel="stylesheet" href="./style.css" type="text/css" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <style>
 | 
				
			||||||
 | 
					        body {
 | 
				
			||||||
 | 
					            height: 100vh;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            padding: 1.5em;
 | 
				
			||||||
 | 
					            padding-bottom: 1em;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            border: 1px solid var(--fg-semi-trans);
 | 
				
			||||||
 | 
					            border-top: none;
 | 
				
			||||||
 | 
					            display: flex;
 | 
				
			||||||
 | 
					            flex-direction: column;
 | 
				
			||||||
 | 
					            box-sizing: border-box;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        select {
 | 
				
			||||||
 | 
					            background: var(--bg);
 | 
				
			||||||
 | 
					            color: var(--fg);
 | 
				
			||||||
 | 
					            padding: 0.3em;
 | 
				
			||||||
 | 
					            margin: -0.3em;
 | 
				
			||||||
 | 
					            border-radius: 6px;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        h1 {
 | 
				
			||||||
 | 
					            margin: 0.4em 0 0;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        p {
 | 
				
			||||||
 | 
					            margin: 1em 0 2em;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        form {
 | 
				
			||||||
 | 
					            display: grid;
 | 
				
			||||||
 | 
					            gap: 1em;
 | 
				
			||||||
 | 
					            margin: 0;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        label {
 | 
				
			||||||
 | 
					            position: relative;
 | 
				
			||||||
 | 
					            display: flex;
 | 
				
			||||||
 | 
					            justify-content: space-between;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        label:has(input[type="checkbox"]),
 | 
				
			||||||
 | 
					        select {
 | 
				
			||||||
 | 
					            cursor: pointer;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        label:not(:last-child)::after {
 | 
				
			||||||
 | 
					            content: "";
 | 
				
			||||||
 | 
					            position: absolute;
 | 
				
			||||||
 | 
					            bottom: -10px;
 | 
				
			||||||
 | 
					            width: 100%;
 | 
				
			||||||
 | 
					            height: 1px;
 | 
				
			||||||
 | 
					            background-color: var(--fg-secondary);
 | 
				
			||||||
 | 
					            opacity: 0.5;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        label div {
 | 
				
			||||||
 | 
					            display: grid;
 | 
				
			||||||
 | 
					            gap: 0.2em;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        label h2 {
 | 
				
			||||||
 | 
					            margin: 0;
 | 
				
			||||||
 | 
					            font-weight: normal;
 | 
				
			||||||
 | 
					            font-size: 1.1rem;
 | 
				
			||||||
 | 
					            line-height: 1rem;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        label span {
 | 
				
			||||||
 | 
					            font-size: 0.9rem;
 | 
				
			||||||
 | 
					            font-weight: 400;
 | 
				
			||||||
 | 
					            color: var(--fg-secondary);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        #buttons {
 | 
				
			||||||
 | 
					            display: flex;
 | 
				
			||||||
 | 
					            justify-content: end;
 | 
				
			||||||
 | 
					            gap: 0.5em;
 | 
				
			||||||
 | 
					            margin-top: auto;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        button {
 | 
				
			||||||
 | 
					            padding: 0.6em;
 | 
				
			||||||
 | 
					            background: red;
 | 
				
			||||||
 | 
					            color: white;
 | 
				
			||||||
 | 
					            border-radius: 6px;
 | 
				
			||||||
 | 
					            border: none;
 | 
				
			||||||
 | 
					            cursor: pointer;
 | 
				
			||||||
 | 
					            transition: 200ms filter;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        button:hover {
 | 
				
			||||||
 | 
					            filter: brightness(0.8);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        #submit {
 | 
				
			||||||
 | 
					            background: green;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    </style>
 | 
				
			||||||
 | 
					</head>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<body>
 | 
				
			||||||
 | 
					    <h1>Welcome to Aerocord - a Vesktop fork meant for Windows Vista, 7 and 8</h1>
 | 
				
			||||||
 | 
					    <p>Let's customise your experience!</p>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <form>
 | 
				
			||||||
 | 
					        <label>
 | 
				
			||||||
 | 
					            <h2>Discord Branch</h2>
 | 
				
			||||||
 | 
					            <select name="discordBranch">
 | 
				
			||||||
 | 
					                <option value="stable">stable</option>
 | 
				
			||||||
 | 
					                <option value="canary">canary</option>
 | 
				
			||||||
 | 
					                <option value="ptb">ptb</option>
 | 
				
			||||||
 | 
					            </select>
 | 
				
			||||||
 | 
					        </label>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <label>
 | 
				
			||||||
 | 
					            <div>
 | 
				
			||||||
 | 
					                <h2>Start with System</h2>
 | 
				
			||||||
 | 
					                <span>Automatically open Aerocord when your computer starts</span>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					            <input type="checkbox" name="autoStart" />
 | 
				
			||||||
 | 
					        </label>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <label>
 | 
				
			||||||
 | 
					            <div>
 | 
				
			||||||
 | 
					                <h2>Rich Presence</h2>
 | 
				
			||||||
 | 
					                <span>Enable Rich presence (game activity) via
 | 
				
			||||||
 | 
					                    <a href="https://github.com/OpenAsar/arrpc" target="_blank">arRPC</a></span>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					            <input type="checkbox" name="richPresence" checked />
 | 
				
			||||||
 | 
					        </label>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <label>
 | 
				
			||||||
 | 
					            <div>
 | 
				
			||||||
 | 
					                <h2>Import Settings</h2>
 | 
				
			||||||
 | 
					                <span>Import Settings from existing Vencord install (if found)</span>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					            <input type="checkbox" name="importSettings" checked />
 | 
				
			||||||
 | 
					        </label>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <label>
 | 
				
			||||||
 | 
					            <div>
 | 
				
			||||||
 | 
					                <h2>Minimise to Tray</h2>
 | 
				
			||||||
 | 
					                <span>Minimise to Tray when closing</span>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					            <input type="checkbox" name="minimizeToTray" checked />
 | 
				
			||||||
 | 
					        </label>
 | 
				
			||||||
 | 
					    </form>
 | 
				
			||||||
 | 
					    <div id="buttons">
 | 
				
			||||||
 | 
					        <button id="cancel">Quit</button>
 | 
				
			||||||
 | 
					        <button id="submit">Submit</button>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					</body>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script>
 | 
				
			||||||
 | 
					    cancel.onclick = () => console.info("cancel");
 | 
				
			||||||
 | 
					    submit.onclick = e => {
 | 
				
			||||||
 | 
					        const form = document.querySelector("form");
 | 
				
			||||||
 | 
					        const formData = new FormData(form);
 | 
				
			||||||
 | 
					        const data = Object.fromEntries(formData.entries());
 | 
				
			||||||
 | 
					        console.info("form:" + JSON.stringify(data));
 | 
				
			||||||
 | 
					        e.preventDefault();
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
							
								
								
									
										45
									
								
								static/views/splash.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,45 @@
 | 
				
			|||||||
 | 
					<head>
 | 
				
			||||||
 | 
					    <link rel="stylesheet" href="./style.css" type="text/css" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <style>
 | 
				
			||||||
 | 
					        body {
 | 
				
			||||||
 | 
					            user-select: none;
 | 
				
			||||||
 | 
					            -webkit-app-region: drag;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        .wrapper {
 | 
				
			||||||
 | 
					            box-sizing: border-box;
 | 
				
			||||||
 | 
					            height: 100%;
 | 
				
			||||||
 | 
					            display: flex;
 | 
				
			||||||
 | 
					            flex-direction: column;
 | 
				
			||||||
 | 
					            justify-content: center;
 | 
				
			||||||
 | 
					            align-items: center;
 | 
				
			||||||
 | 
					            border-radius: 8px;
 | 
				
			||||||
 | 
					            border: 1px solid var(--fg-semi-trans);
 | 
				
			||||||
 | 
					            color: rgb(255, 238, 0)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        p {
 | 
				
			||||||
 | 
					            text-align: center;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        h6 {
 | 
				
			||||||
 | 
					            text-align: center;
 | 
				
			||||||
 | 
					            color: rgb(78, 78, 78)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        img {
 | 
				
			||||||
 | 
					            width: 128px;
 | 
				
			||||||
 | 
					            height: 128px;
 | 
				
			||||||
 | 
					            image-rendering: pixelated;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    </style>
 | 
				
			||||||
 | 
					</head>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<body>
 | 
				
			||||||
 | 
					    <div class="wrapper">
 | 
				
			||||||
 | 
					        <img draggable="false" src="../shiggy.gif" alt="Windows 7 jumpscare" role="presentation" />
 | 
				
			||||||
 | 
					        <p>Loading Aerocord...</p>
 | 
				
			||||||
 | 
					        <h6>Made by the RandomServer Community!</h6>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					</body>
 | 
				
			||||||
							
								
								
									
										30
									
								
								static/views/style.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,30 @@
 | 
				
			|||||||
 | 
					:root {
 | 
				
			||||||
 | 
					    --bg: black;
 | 
				
			||||||
 | 
					    --fg: white;
 | 
				
			||||||
 | 
					    --fg-secondary: #313338;
 | 
				
			||||||
 | 
					    --fg-semi-trans: rgb(0 0 0 / 0.2);
 | 
				
			||||||
 | 
					    --link: #006ce7;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@media (prefers-color-scheme: dark) {
 | 
				
			||||||
 | 
					    :root {
 | 
				
			||||||
 | 
					        --bg: hsl(223 6.7% 20.6%);
 | 
				
			||||||
 | 
					        --fg: cyan;
 | 
				
			||||||
 | 
					        --fg-secondary: #b5bac1;
 | 
				
			||||||
 | 
					        --fg-semi-trans: rgb(255 255 255 / 0.2);
 | 
				
			||||||
 | 
					        --link: #00a8fc;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					body {
 | 
				
			||||||
 | 
					    font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell,
 | 
				
			||||||
 | 
					        "Open Sans", "Helvetica Neue", sans-serif;
 | 
				
			||||||
 | 
					    margin: 0;
 | 
				
			||||||
 | 
					    padding: 0;
 | 
				
			||||||
 | 
					    background: var(--bg);
 | 
				
			||||||
 | 
					    color: var(--fg);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					a {
 | 
				
			||||||
 | 
					    color: var(--link);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										123
									
								
								static/views/updater.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,123 @@
 | 
				
			|||||||
 | 
					<head>
 | 
				
			||||||
 | 
					    <link rel="stylesheet" href="./style.css" type="text/css" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <style>
 | 
				
			||||||
 | 
					        .wrapper {
 | 
				
			||||||
 | 
					            display: flex;
 | 
				
			||||||
 | 
					            flex-direction: column;
 | 
				
			||||||
 | 
					            justify-content: space-between;
 | 
				
			||||||
 | 
					            box-sizing: border-box;
 | 
				
			||||||
 | 
					            min-height: 100%;
 | 
				
			||||||
 | 
					            padding: 1em;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        h1 {
 | 
				
			||||||
 | 
					            text-align: center;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        .buttons {
 | 
				
			||||||
 | 
					            display: grid;
 | 
				
			||||||
 | 
					            grid-template-columns: 1fr 1fr;
 | 
				
			||||||
 | 
					            gap: 0.5em;
 | 
				
			||||||
 | 
					            margin-top: 0.25em;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        button {
 | 
				
			||||||
 | 
					            cursor: pointer;
 | 
				
			||||||
 | 
					            padding: 0.5em;
 | 
				
			||||||
 | 
					            color: var(--fg);
 | 
				
			||||||
 | 
					            border: none;
 | 
				
			||||||
 | 
					            border-radius: 3px;
 | 
				
			||||||
 | 
					            font-weight: bold;
 | 
				
			||||||
 | 
					            transition: filter 0.2 ease-in-out;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        button:hover,
 | 
				
			||||||
 | 
					        button:active {
 | 
				
			||||||
 | 
					            filter: brightness(0.9);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        .green {
 | 
				
			||||||
 | 
					            background-color: #248046;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        .red {
 | 
				
			||||||
 | 
					            background-color: #ed4245;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    </style>
 | 
				
			||||||
 | 
					</head>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<body>
 | 
				
			||||||
 | 
					    <div class="wrapper">
 | 
				
			||||||
 | 
					        <section>
 | 
				
			||||||
 | 
					            <h1>Update Available</h1>
 | 
				
			||||||
 | 
					            <p>There's a new update for Aerocord! Update now to get new fixes and features!</p>
 | 
				
			||||||
 | 
					            <p>
 | 
				
			||||||
 | 
					                Current: <span id="current"></span>
 | 
				
			||||||
 | 
					                <br />
 | 
				
			||||||
 | 
					                Latest: <span id="latest"></span>
 | 
				
			||||||
 | 
					            </p>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <h2>Changelog</h2>
 | 
				
			||||||
 | 
					            <p id="changelog">Loading...</p>
 | 
				
			||||||
 | 
					        </section>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <section>
 | 
				
			||||||
 | 
					            <label id="disable-remind">
 | 
				
			||||||
 | 
					                <input type="checkbox" />
 | 
				
			||||||
 | 
					                <span>Do not remind again for </span>
 | 
				
			||||||
 | 
					            </label>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <div class="buttons">
 | 
				
			||||||
 | 
					                <button name="download" class="green">Download Update</button>
 | 
				
			||||||
 | 
					                <button name="close" class="red">Close</button>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        </section>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					</body>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script type="module">
 | 
				
			||||||
 | 
					    const data = await Updater.getData();
 | 
				
			||||||
 | 
					    document.getElementById("current").textContent = data.currentVersion;
 | 
				
			||||||
 | 
					    document.getElementById("latest").textContent = data.latestVersion;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    document.querySelector("#disable-remind > span").textContent += data.latestVersion;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function checkDisableRemind() {
 | 
				
			||||||
 | 
					        const checkbox = document.querySelector("#disable-remind > input");
 | 
				
			||||||
 | 
					        if (checkbox.checked) {
 | 
				
			||||||
 | 
					            Updater.ignore();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const onClicks = {
 | 
				
			||||||
 | 
					        download() {
 | 
				
			||||||
 | 
					            checkDisableRemind();
 | 
				
			||||||
 | 
					            Updater.download();
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        close() {
 | 
				
			||||||
 | 
					            checkDisableRemind();
 | 
				
			||||||
 | 
					            Updater.close();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (const name in onClicks) {
 | 
				
			||||||
 | 
					        document.querySelectorAll(`button[name="${name}"]`).forEach(button => {
 | 
				
			||||||
 | 
					            button.addEventListener("click", onClicks[name]);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script type="module">
 | 
				
			||||||
 | 
					    import { micromark } from "https://esm.sh/micromark@3?bundle";
 | 
				
			||||||
 | 
					    import { gfm, gfmHtml } from "https://esm.sh/micromark-extension-gfm@2?bundle";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const changelog = (await Updater.getData()).release.body;
 | 
				
			||||||
 | 
					    if (changelog)
 | 
				
			||||||
 | 
					        document.getElementById("changelog").innerHTML = micromark(changelog, {
 | 
				
			||||||
 | 
					            extensions: [gfm()],
 | 
				
			||||||
 | 
					            htmlExtensions: [gfmHtml()]
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					            .replace(/h1>/g, "h3>")
 | 
				
			||||||
 | 
					            .replace(/<a /g, '<a target="_blank" ');
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||