2022-01-30 19:48:32 +00:00
// To allow seamless switching between custom titlebar and native os titlebar,
// I had to add most of the window creation code here to split both into seperete functions
// WHY? Because I can't use the same code for both due to annoying bug with value `frame` not responding to variables
// I'm sorry for this mess but I'm not sure how to fix it.
2023-06-10 21:04:27 +00:00
import { BrowserWindow , MessageBoxOptions , app , dialog , nativeImage , shell } from "electron" ;
2022-01-30 19:48:32 +00:00
import path from "path" ;
2022-10-08 15:43:08 +00:00
import {
2023-05-08 19:24:30 +00:00
contentPath ,
2022-10-08 15:43:08 +00:00
firstRun ,
getConfig ,
2023-06-11 16:50:07 +00:00
getWindowState ,
2023-05-08 19:24:30 +00:00
modInstallState ,
2023-07-24 19:51:08 +00:00
registerGlobalKeybinds ,
2022-10-08 15:43:08 +00:00
setConfig ,
setLang ,
setWindowState ,
2022-12-10 19:33:35 +00:00
sleep ,
2023-05-08 19:24:30 +00:00
transparency
2022-10-08 15:43:08 +00:00
} from "./utils" ;
2022-08-22 09:24:55 +00:00
import { registerIpc } from "./ipc" ;
import { setMenu } from "./menu" ;
2022-06-16 21:09:41 +00:00
import * as fs from "fs" ;
2022-02-26 21:26:16 +00:00
import contextMenu from "electron-context-menu" ;
2022-05-14 17:55:06 +00:00
import os from "os" ;
2022-08-25 12:57:28 +00:00
import { tray } from "./tray" ;
2022-08-25 14:42:54 +00:00
import { iconPath } from "./main" ;
2023-06-10 20:54:46 +00:00
import { createSetupWindow } from "./setup/main" ;
2022-01-30 19:48:32 +00:00
export let mainWindow : BrowserWindow ;
2022-04-19 13:56:04 +00:00
export let inviteWindow : BrowserWindow ;
2023-07-24 14:56:57 +00:00
let forceQuit = false ;
2023-05-08 19:24:30 +00:00
let osType = os . type ( ) ;
2022-02-26 21:26:16 +00:00
contextMenu ( {
2022-03-04 17:53:18 +00:00
showSaveImageAs : true ,
showCopyImageAddress : true ,
2022-09-25 18:30:09 +00:00
showSearchWithGoogle : false ,
2023-02-25 18:08:21 +00:00
showSearchWithDuckDuckGo : false ,
2023-05-08 19:24:30 +00:00
prepend : ( _defaultActions , parameters ) = > [
2022-09-25 18:30:09 +00:00
{
label : "Search with Google" ,
// Only show it when right-clicking text
visible : parameters.selectionText.trim ( ) . length > 0 ,
click : ( ) = > {
shell . openExternal ( ` https://google.com/search?q= ${ encodeURIComponent ( parameters . selectionText ) } ` ) ;
}
} ,
{
label : "Search with DuckDuckGo" ,
// Only show it when right-clicking text
visible : parameters.selectionText.trim ( ) . length > 0 ,
click : ( ) = > {
shell . openExternal ( ` https://duckduckgo.com/?q= ${ encodeURIComponent ( parameters . selectionText ) } ` ) ;
}
}
]
2022-02-26 21:26:16 +00:00
} ) ;
2023-05-08 19:24:30 +00:00
async function doAfterDefiningTheWindow ( ) : Promise < void > {
2023-06-11 16:50:07 +00:00
if ( ( await getWindowState ( "isMaximized" ) ) ? ? false ) {
mainWindow . setSize ( 835 , 600 ) ; //just so the whole thing doesn't cover whole screen
mainWindow . maximize ( ) ;
mainWindow . webContents . executeJavaScript ( ` document.body.setAttribute("isMaximized", ""); ` ) ;
mainWindow . hide ( ) ; // please don't flashbang the user
2022-12-13 12:13:25 +00:00
}
2023-08-05 14:47:17 +00:00
if ( ( await getConfig ( "windowStyle" ) ) == "transparency" && process . platform === "win32" ) {
2023-08-02 12:34:15 +00:00
mainWindow . setBackgroundMaterial ( "mica" ) ;
2023-07-15 12:21:49 +00:00
if ( ( await getConfig ( "startMinimized" ) ) == false ) {
mainWindow . show ( ) ;
}
2022-10-08 15:43:08 +00:00
}
2023-05-08 19:24:30 +00:00
let ignoreProtocolWarning = await getConfig ( "ignoreProtocolWarning" ) ;
2022-03-04 17:53:18 +00:00
registerIpc ( ) ;
2022-07-11 17:13:52 +00:00
if ( await getConfig ( "mobileMode" ) ) {
2022-08-22 09:24:55 +00:00
mainWindow . webContents . userAgent =
"Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.149 Mobile Safari/537.36" ;
2022-07-11 17:13:52 +00:00
} else {
// A little sloppy but it works :p
2022-08-22 09:24:55 +00:00
if ( osType == "Windows_NT" ) {
2023-05-08 19:24:30 +00:00
osType = ` Windows ${ os . release ( ) . split ( "." ) [ 0 ] } ( ${ os . release ( ) } ) ` ;
2022-07-11 17:13:52 +00:00
}
2023-04-25 08:57:49 +00:00
mainWindow . webContents . userAgent = ` Mozilla/5.0 (X11; ${ osType } ${ os . arch ( ) } ) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36 ` ; //fake useragent for screenshare to work
2022-06-03 16:05:28 +00:00
}
2023-05-08 19:24:30 +00:00
app . on ( "second-instance" , ( _event , _commandLine , _workingDirectory , additionalData ) = > {
2022-12-13 18:45:21 +00:00
// Print out data received from the second instance.
console . log ( additionalData ) ;
2022-07-11 17:13:52 +00:00
2022-12-13 18:45:21 +00:00
// Someone tried to run a second instance, we should focus our window.
if ( mainWindow ) {
if ( mainWindow . isMinimized ( ) ) mainWindow . restore ( ) ;
mainWindow . show ( ) ;
mainWindow . focus ( ) ;
}
} ) ;
2023-07-24 14:56:57 +00:00
app . on ( "activate" , function ( ) {
app . show ( ) ;
} ) ;
2022-08-22 09:24:55 +00:00
mainWindow . webContents . setWindowOpenHandler ( ( { url } ) = > {
2022-11-19 18:04:14 +00:00
// Allow about:blank (used by Vencord QuickCss popup)
2022-11-21 05:50:21 +00:00
if ( url === "about:blank" ) return { action : "allow" } ;
2022-12-13 10:02:09 +00:00
// Allow Discord stream popout
2023-04-25 08:57:49 +00:00
if (
url === "https://discord.com/popout" ||
url === "https://canary.discord.com/popout" ||
url === "https://ptb.discord.com/popout"
)
return { action : "allow" } ;
2022-12-14 05:36:09 +00:00
if ( url . startsWith ( "https:" ) || url . startsWith ( "http:" ) || url . startsWith ( "mailto:" ) ) {
2022-06-03 13:18:36 +00:00
shell . openExternal ( url ) ;
2023-05-08 19:24:30 +00:00
} else if ( ignoreProtocolWarning ) {
shell . openExternal ( url ) ;
2022-06-03 13:18:36 +00:00
} else {
2023-06-10 21:04:27 +00:00
const options : MessageBoxOptions = {
2023-05-08 19:24:30 +00:00
type : "question" ,
buttons : [ "Yes, please" , "No, I don't" ] ,
defaultId : 1 ,
title : url ,
message : ` Do you want to open ${ url } ? ` ,
detail : "This url was detected to not use normal browser protocols. It could mean that this url leads to a local program on your computer. Please check if you recognise it, before proceeding!" ,
checkboxLabel : "Remember my answer and ignore this warning for future sessions" ,
checkboxChecked : false
} ;
2022-06-03 13:18:36 +00:00
2023-05-08 19:24:30 +00:00
dialog . showMessageBox ( mainWindow , options ) . then ( ( { response , checkboxChecked } ) = > {
console . log ( response , checkboxChecked ) ;
if ( checkboxChecked ) {
2022-06-03 13:18:36 +00:00
if ( response == 0 ) {
2023-05-08 19:24:30 +00:00
setConfig ( "ignoreProtocolWarning" , true ) ;
2022-06-03 13:18:36 +00:00
} else {
2023-05-08 19:24:30 +00:00
setConfig ( "ignoreProtocolWarning" , false ) ;
2022-06-03 13:18:36 +00:00
}
2023-05-08 19:24:30 +00:00
}
if ( response == 0 ) {
shell . openExternal ( url ) ;
}
} ) ;
2022-06-03 13:18:36 +00:00
}
2022-08-22 09:24:55 +00:00
return { action : "deny" } ;
2022-03-04 17:53:18 +00:00
} ) ;
2022-12-25 20:43:35 +00:00
if ( ( await getConfig ( "useLegacyCapturer" ) ) == false ) {
console . log ( "Starting screenshare module..." ) ;
import ( "./screenshare/main" ) ;
}
2022-12-26 22:29:29 +00:00
mainWindow . webContents . session . webRequest . onBeforeRequest (
{ urls : [ "https://*/api/v*/science" , "https://sentry.io/*" , "https://*.nel.cloudflare.com/*" ] } ,
( _ , callback ) = > callback ( { cancel : true } )
) ;
2022-12-27 10:34:26 +00:00
if ( ( await getConfig ( "trayIcon" ) ) == "default" || ( await getConfig ( "dynamicIcon" ) ) ) {
2023-05-08 19:24:30 +00:00
mainWindow . webContents . on ( "page-favicon-updated" , async ( ) = > {
let faviconBase64 = await mainWindow . webContents . executeJavaScript ( `
2022-08-25 12:57:28 +00:00
var getFavicon = function ( ) {
var favicon = undefined ;
var nodeList = document . getElementsByTagName ( "link" ) ;
for ( var i = 0 ; i < nodeList . length ; i ++ )
{
if ( ( nodeList [ i ] . getAttribute ( "rel" ) == "icon" ) || ( nodeList [ i ] . getAttribute ( "rel" ) == "shortcut icon" ) )
{
favicon = nodeList [ i ] . getAttribute ( "href" ) ;
}
}
2022-11-19 18:04:14 +00:00
return favicon ;
2022-08-25 12:57:28 +00:00
}
getFavicon ( )
2022-09-25 18:30:09 +00:00
` );
2023-05-08 19:24:30 +00:00
let buf = Buffer . from ( faviconBase64 . replace ( /^data:image\/\w+;base64,/ , "" ) , "base64" ) ;
2022-08-25 14:10:26 +00:00
fs . writeFileSync ( path . join ( app . getPath ( "temp" ) , "/" , "tray.png" ) , buf , "utf-8" ) ;
let trayPath = nativeImage . createFromPath ( path . join ( app . getPath ( "temp" ) , "/" , "tray.png" ) ) ;
2022-09-25 18:30:09 +00:00
if ( process . platform === "darwin" && trayPath . getSize ( ) . height > 22 )
trayPath = trayPath . resize ( { height : 22 } ) ;
2022-12-13 19:30:00 +00:00
if ( process . platform === "win32" && trayPath . getSize ( ) . height > 32 )
trayPath = trayPath . resize ( { height : 32 } ) ;
2023-06-11 16:50:07 +00:00
if ( await getConfig ( "tray" ) ) {
if ( ( await getConfig ( "trayIcon" ) ) == "default" ) {
tray . setImage ( trayPath ) ;
}
2023-06-10 17:49:32 +00:00
}
if ( await getConfig ( "dynamicIcon" ) ) {
2022-12-27 10:34:26 +00:00
mainWindow . setIcon ( trayPath ) ;
}
2022-09-25 18:30:09 +00:00
} ) ;
2023-06-10 17:31:14 +00:00
mainWindow . webContents . on ( "page-title-updated" , async ( e , title ) = > {
const armCordSuffix = " - ArmCord" ; /* identify */
if ( ! title . endsWith ( armCordSuffix ) ) {
e . preventDefault ( ) ;
await mainWindow . webContents . executeJavaScript (
` document.title = ' ${ ( title . split ( " | " ) [ 1 ] ? ? title ) + armCordSuffix } ' `
) ;
}
} ) ;
2022-08-25 12:57:28 +00:00
}
2022-06-16 21:09:41 +00:00
const userDataPath = app . getPath ( "userData" ) ;
2023-05-08 19:24:30 +00:00
const themesFolder = ` ${ userDataPath } /themes/ ` ;
2022-06-16 21:09:41 +00:00
if ( ! fs . existsSync ( themesFolder ) ) {
fs . mkdirSync ( themesFolder ) ;
console . log ( "Created missing theme folder" ) ;
}
2023-05-13 20:09:01 +00:00
if ( ! fs . existsSync ( ` ${ userDataPath } /disabled.txt ` ) ) {
fs . writeFileSync ( path . join ( userDataPath , "/disabled.txt" ) , "" ) ;
}
2023-07-24 19:51:08 +00:00
registerGlobalKeybinds ( ) ;
2022-08-22 09:24:55 +00:00
mainWindow . webContents . on ( "did-finish-load" , ( ) = > {
2023-07-18 14:54:04 +00:00
fs . readdirSync ( themesFolder ) . forEach ( ( file ) = > {
try {
const manifest = fs . readFileSync ( ` ${ themesFolder } / ${ file } /manifest.json ` , "utf8" ) ;
let themeFile = JSON . parse ( manifest ) ;
if (
fs
. readFileSync ( path . join ( userDataPath , "/disabled.txt" ) )
. toString ( )
. includes ( themeFile . name . replace ( " " , "-" ) )
) {
console . log ( ` %cSkipped ${ themeFile . name } made by ${ themeFile . author } ` , "color:red" ) ;
} else {
mainWindow . webContents . send (
"themeLoader" ,
fs . readFileSync ( ` ${ themesFolder } / ${ file } / ${ themeFile . theme } ` , "utf-8" )
) ;
console . log ( ` %cLoaded ${ themeFile . name } made by ${ themeFile . author } ` , "color:red" ) ;
2023-05-13 20:09:01 +00:00
}
2023-07-18 14:54:04 +00:00
} catch ( err ) {
console . error ( err ) ;
}
} ) ;
2022-06-16 21:09:41 +00:00
} ) ;
2022-08-25 16:40:43 +00:00
await setMenu ( ) ;
2022-03-04 17:53:18 +00:00
mainWindow . on ( "close" , async ( e ) = > {
2023-07-24 14:56:57 +00:00
if ( process . platform === "darwin" && forceQuit ) {
mainWindow . close ( ) ;
} else {
let [ width , height ] = mainWindow . getSize ( ) ;
await setWindowState ( {
width ,
height ,
isMaximized : mainWindow.isMaximized ( ) ,
x : mainWindow.getPosition ( ) [ 0 ] ,
y : mainWindow.getPosition ( ) [ 1 ]
} ) ;
if ( await getConfig ( "minimizeToTray" ) ) {
e . preventDefault ( ) ;
mainWindow . hide ( ) ;
} else if ( ! ( await getConfig ( "minimizeToTray" ) ) ) {
e . preventDefault ( ) ;
app . quit ( ) ;
}
2022-03-04 17:53:18 +00:00
}
} ) ;
2023-07-24 14:56:57 +00:00
if ( process . platform === "darwin" ) {
app . on ( "before-quit" , function ( event ) {
if ( ! forceQuit ) {
event . preventDefault ( ) ;
forceQuit = true ;
app . quit ( ) ;
}
} ) ;
}
2022-08-22 09:24:55 +00:00
mainWindow . on ( "focus" , ( ) = > {
mainWindow . webContents . executeJavaScript ( ` document.body.removeAttribute("unFocused"); ` ) ;
} ) ;
mainWindow . on ( "blur" , ( ) = > {
mainWindow . webContents . executeJavaScript ( ` document.body.setAttribute("unFocused", ""); ` ) ;
} ) ;
mainWindow . on ( "maximize" , ( ) = > {
mainWindow . webContents . executeJavaScript ( ` document.body.setAttribute("isMaximized", ""); ` ) ;
} ) ;
mainWindow . on ( "unmaximize" , ( ) = > {
mainWindow . webContents . executeJavaScript ( ` document.body.removeAttribute("isMaximized"); ` ) ;
} ) ;
2022-03-04 17:53:18 +00:00
console . log ( contentPath ) ;
2022-05-22 11:52:26 +00:00
if ( ( await getConfig ( "inviteWebsocket" ) ) == true ) {
2022-11-22 14:03:54 +00:00
require ( "arrpc" ) ;
2022-11-19 22:15:05 +00:00
//await startServer();
2022-04-19 17:59:52 +00:00
}
2022-07-11 18:19:50 +00:00
if ( firstRun ) {
2023-06-10 20:54:46 +00:00
mainWindow . close ( ) ;
2023-06-11 16:50:07 +00:00
}
2024-01-19 10:22:41 +00:00
//loadURL broke for no good reason after E28
2024-01-19 16:14:25 +00:00
mainWindow . loadFile ( ` ${ __dirname } /splash/redirect.html ` ) ;
2024-01-19 10:22:41 +00:00
2023-06-11 16:50:07 +00:00
if ( await getConfig ( "skipSplash" ) ) {
2022-12-13 12:13:25 +00:00
mainWindow . show ( ) ;
}
2022-01-30 19:48:32 +00:00
}
2023-05-13 21:48:11 +00:00
export async function createCustomWindow ( ) : Promise < void > {
2022-03-04 17:53:18 +00:00
mainWindow = new BrowserWindow ( {
2023-06-11 16:50:07 +00:00
width : ( await getWindowState ( "width" ) ) ? ? 835 ,
height : ( await getWindowState ( "height" ) ) ? ? 600 ,
x : await getWindowState ( "x" ) ,
y : await getWindowState ( "y" ) ,
2022-03-04 17:53:18 +00:00
title : "ArmCord" ,
2022-12-13 12:13:25 +00:00
show : false ,
2022-03-04 17:53:18 +00:00
darkTheme : true ,
2022-08-25 14:42:54 +00:00
icon : iconPath ,
2022-03-04 17:53:18 +00:00
frame : false ,
2022-12-13 18:45:21 +00:00
backgroundColor : "#202225" ,
2022-03-04 17:53:18 +00:00
autoHideMenuBar : true ,
webPreferences : {
2023-07-29 11:26:36 +00:00
webviewTag : true ,
2022-08-25 14:10:26 +00:00
sandbox : false ,
2022-03-04 17:53:18 +00:00
preload : path.join ( __dirname , "preload/preload.js" ) ,
2023-05-13 21:48:11 +00:00
spellcheck : await getConfig ( "spellcheck" )
2022-03-04 17:53:18 +00:00
}
} ) ;
doAfterDefiningTheWindow ( ) ;
2022-01-30 19:48:32 +00:00
}
2023-05-13 21:48:11 +00:00
export async function createNativeWindow ( ) : Promise < void > {
2022-03-04 17:53:18 +00:00
mainWindow = new BrowserWindow ( {
2023-06-11 16:50:07 +00:00
width : ( await getWindowState ( "width" ) ) ? ? 835 ,
height : ( await getWindowState ( "height" ) ) ? ? 600 ,
x : await getWindowState ( "x" ) ,
y : await getWindowState ( "y" ) ,
2022-03-04 17:53:18 +00:00
title : "ArmCord" ,
darkTheme : true ,
2022-08-25 14:42:54 +00:00
icon : iconPath ,
2022-12-13 12:13:25 +00:00
show : false ,
2022-03-04 17:53:18 +00:00
frame : true ,
2022-12-13 18:45:21 +00:00
backgroundColor : "#202225" ,
2022-03-04 17:53:18 +00:00
autoHideMenuBar : true ,
webPreferences : {
2023-07-29 11:26:36 +00:00
webviewTag : true ,
2022-08-25 14:10:26 +00:00
sandbox : false ,
2022-03-04 17:53:18 +00:00
preload : path.join ( __dirname , "preload/preload.js" ) ,
2023-05-13 21:48:11 +00:00
spellcheck : await getConfig ( "spellcheck" )
2022-03-04 17:53:18 +00:00
}
} ) ;
doAfterDefiningTheWindow ( ) ;
2022-02-26 21:26:16 +00:00
}
2023-05-13 21:48:11 +00:00
export async function createTransparentWindow ( ) : Promise < void > {
2022-10-08 15:43:08 +00:00
mainWindow = new BrowserWindow ( {
2023-06-11 16:50:07 +00:00
width : ( await getWindowState ( "width" ) ) ? ? 835 ,
height : ( await getWindowState ( "height" ) ) ? ? 600 ,
x : await getWindowState ( "x" ) ,
y : await getWindowState ( "y" ) ,
2022-10-08 15:43:08 +00:00
title : "ArmCord" ,
darkTheme : true ,
icon : iconPath ,
frame : true ,
backgroundColor : "#00000000" ,
show : false ,
autoHideMenuBar : true ,
webPreferences : {
sandbox : false ,
2023-07-29 11:26:36 +00:00
webviewTag : true ,
2022-10-08 15:43:08 +00:00
preload : path.join ( __dirname , "preload/preload.js" ) ,
2023-05-13 21:48:11 +00:00
spellcheck : await getConfig ( "spellcheck" )
2022-10-08 15:43:08 +00:00
}
} ) ;
doAfterDefiningTheWindow ( ) ;
}
2023-05-13 21:48:11 +00:00
export async function createInviteWindow ( code : string ) : Promise < void > {
2022-04-19 13:56:04 +00:00
inviteWindow = new BrowserWindow ( {
width : 800 ,
height : 600 ,
title : "ArmCord Invite Manager" ,
darkTheme : true ,
2022-08-25 14:42:54 +00:00
icon : iconPath ,
2022-04-19 13:56:04 +00:00
frame : true ,
autoHideMenuBar : true ,
webPreferences : {
2022-08-25 14:10:26 +00:00
sandbox : false ,
2023-05-13 21:48:11 +00:00
spellcheck : await getConfig ( "spellcheck" )
2022-04-19 13:56:04 +00:00
}
} ) ;
2023-05-08 19:24:30 +00:00
let formInviteURL = ` https://discord.com/invite/ ${ code } ` ;
2022-11-22 14:34:24 +00:00
inviteWindow . webContents . session . webRequest . onBeforeRequest ( ( details , callback ) = > {
if ( details . url . includes ( "ws://" ) ) return callback ( { cancel : true } ) ;
return callback ( { } ) ;
} ) ;
inviteWindow . loadURL ( formInviteURL ) ;
inviteWindow . webContents . once ( "did-finish-load" , ( ) = > {
2023-06-17 16:33:07 +00:00
if ( ! mainWindow . webContents . isLoading ( ) ) {
inviteWindow . show ( ) ;
inviteWindow . webContents . once ( "will-navigate" , ( ) = > {
inviteWindow . close ( ) ;
} ) ;
}
2022-11-22 14:34:24 +00:00
} ) ;
2022-05-22 11:52:26 +00:00
}