├── .gitignore ├── src ├── utils │ ├── paths.js │ ├── buildInfo.js │ └── securityUtils.js ├── appSettings.js ├── splashScreen.js ├── images │ ├── badges │ │ ├── badge-1.ico │ │ ├── badge-10.ico │ │ ├── badge-11.ico │ │ ├── badge-2.ico │ │ ├── badge-3.ico │ │ ├── badge-4.ico │ │ ├── badge-5.ico │ │ ├── badge-6.ico │ │ ├── badge-7.ico │ │ ├── badge-8.ico │ │ └── badge-9.ico │ ├── systemtray │ │ ├── tray.png │ │ ├── tray-muted.png │ │ ├── tray-unread.png │ │ ├── tray-connected.png │ │ ├── tray-deafened.png │ │ └── tray-speaking.png │ ├── thumbar │ │ ├── darwin │ │ │ ├── mute.png │ │ │ ├── video.png │ │ │ ├── deafen.png │ │ │ ├── deafen-off.png │ │ │ ├── disconnect.png │ │ │ ├── mute-off.png │ │ │ └── video-off.png │ │ └── win32 │ │ │ ├── deafen.png │ │ │ ├── mute.png │ │ │ ├── video.png │ │ │ ├── mute-off.png │ │ │ ├── deafen-off.png │ │ │ ├── disconnect.png │ │ │ ├── mute-light.png │ │ │ ├── video-light.png │ │ │ ├── video-off.png │ │ │ ├── deafen-light.png │ │ │ ├── deafen-off-light.png │ │ │ ├── disconnect-light.png │ │ │ ├── mute-off-light.png │ │ │ └── video-off-light.png │ ├── close.svg │ └── discord.svg ├── bootstrapModules.js ├── ipcMain.js ├── index.js ├── utils.js ├── Constants.js ├── appBadge.js ├── systemTray.js └── mainScreen.js ├── README.md ├── test.sh └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | *.asar 2 | -------------------------------------------------------------------------------- /src/utils/paths.js: -------------------------------------------------------------------------------- 1 | module.exports = require('../bootstrapModules').paths; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OpenCore 2 | Open-source alternative of Discord's desktop_core 3 | -------------------------------------------------------------------------------- /src/appSettings.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./bootstrapModules').appSettings; -------------------------------------------------------------------------------- /src/splashScreen.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./bootstrapModules').splashScreen; -------------------------------------------------------------------------------- /src/utils/buildInfo.js: -------------------------------------------------------------------------------- 1 | module.exports = require('../bootstrapModules').buildInfo; -------------------------------------------------------------------------------- /src/utils/securityUtils.js: -------------------------------------------------------------------------------- 1 | module.exports = require('../bootstrapModules').securityUtils; -------------------------------------------------------------------------------- /src/images/badges/badge-1.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GooseMod/OpenCore/HEAD/src/images/badges/badge-1.ico -------------------------------------------------------------------------------- /src/images/badges/badge-10.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GooseMod/OpenCore/HEAD/src/images/badges/badge-10.ico -------------------------------------------------------------------------------- /src/images/badges/badge-11.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GooseMod/OpenCore/HEAD/src/images/badges/badge-11.ico -------------------------------------------------------------------------------- /src/images/badges/badge-2.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GooseMod/OpenCore/HEAD/src/images/badges/badge-2.ico -------------------------------------------------------------------------------- /src/images/badges/badge-3.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GooseMod/OpenCore/HEAD/src/images/badges/badge-3.ico -------------------------------------------------------------------------------- /src/images/badges/badge-4.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GooseMod/OpenCore/HEAD/src/images/badges/badge-4.ico -------------------------------------------------------------------------------- /src/images/badges/badge-5.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GooseMod/OpenCore/HEAD/src/images/badges/badge-5.ico -------------------------------------------------------------------------------- /src/images/badges/badge-6.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GooseMod/OpenCore/HEAD/src/images/badges/badge-6.ico -------------------------------------------------------------------------------- /src/images/badges/badge-7.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GooseMod/OpenCore/HEAD/src/images/badges/badge-7.ico -------------------------------------------------------------------------------- /src/images/badges/badge-8.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GooseMod/OpenCore/HEAD/src/images/badges/badge-8.ico -------------------------------------------------------------------------------- /src/images/badges/badge-9.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GooseMod/OpenCore/HEAD/src/images/badges/badge-9.ico -------------------------------------------------------------------------------- /src/images/systemtray/tray.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GooseMod/OpenCore/HEAD/src/images/systemtray/tray.png -------------------------------------------------------------------------------- /src/images/thumbar/darwin/mute.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GooseMod/OpenCore/HEAD/src/images/thumbar/darwin/mute.png -------------------------------------------------------------------------------- /src/images/thumbar/darwin/video.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GooseMod/OpenCore/HEAD/src/images/thumbar/darwin/video.png -------------------------------------------------------------------------------- /src/images/thumbar/win32/deafen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GooseMod/OpenCore/HEAD/src/images/thumbar/win32/deafen.png -------------------------------------------------------------------------------- /src/images/thumbar/win32/mute.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GooseMod/OpenCore/HEAD/src/images/thumbar/win32/mute.png -------------------------------------------------------------------------------- /src/images/thumbar/win32/video.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GooseMod/OpenCore/HEAD/src/images/thumbar/win32/video.png -------------------------------------------------------------------------------- /src/images/systemtray/tray-muted.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GooseMod/OpenCore/HEAD/src/images/systemtray/tray-muted.png -------------------------------------------------------------------------------- /src/images/systemtray/tray-unread.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GooseMod/OpenCore/HEAD/src/images/systemtray/tray-unread.png -------------------------------------------------------------------------------- /src/images/thumbar/darwin/deafen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GooseMod/OpenCore/HEAD/src/images/thumbar/darwin/deafen.png -------------------------------------------------------------------------------- /src/images/thumbar/win32/mute-off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GooseMod/OpenCore/HEAD/src/images/thumbar/win32/mute-off.png -------------------------------------------------------------------------------- /src/images/systemtray/tray-connected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GooseMod/OpenCore/HEAD/src/images/systemtray/tray-connected.png -------------------------------------------------------------------------------- /src/images/systemtray/tray-deafened.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GooseMod/OpenCore/HEAD/src/images/systemtray/tray-deafened.png -------------------------------------------------------------------------------- /src/images/systemtray/tray-speaking.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GooseMod/OpenCore/HEAD/src/images/systemtray/tray-speaking.png -------------------------------------------------------------------------------- /src/images/thumbar/darwin/deafen-off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GooseMod/OpenCore/HEAD/src/images/thumbar/darwin/deafen-off.png -------------------------------------------------------------------------------- /src/images/thumbar/darwin/disconnect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GooseMod/OpenCore/HEAD/src/images/thumbar/darwin/disconnect.png -------------------------------------------------------------------------------- /src/images/thumbar/darwin/mute-off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GooseMod/OpenCore/HEAD/src/images/thumbar/darwin/mute-off.png -------------------------------------------------------------------------------- /src/images/thumbar/darwin/video-off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GooseMod/OpenCore/HEAD/src/images/thumbar/darwin/video-off.png -------------------------------------------------------------------------------- /src/images/thumbar/win32/deafen-off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GooseMod/OpenCore/HEAD/src/images/thumbar/win32/deafen-off.png -------------------------------------------------------------------------------- /src/images/thumbar/win32/disconnect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GooseMod/OpenCore/HEAD/src/images/thumbar/win32/disconnect.png -------------------------------------------------------------------------------- /src/images/thumbar/win32/mute-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GooseMod/OpenCore/HEAD/src/images/thumbar/win32/mute-light.png -------------------------------------------------------------------------------- /src/images/thumbar/win32/video-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GooseMod/OpenCore/HEAD/src/images/thumbar/win32/video-light.png -------------------------------------------------------------------------------- /src/images/thumbar/win32/video-off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GooseMod/OpenCore/HEAD/src/images/thumbar/win32/video-off.png -------------------------------------------------------------------------------- /src/images/thumbar/win32/deafen-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GooseMod/OpenCore/HEAD/src/images/thumbar/win32/deafen-light.png -------------------------------------------------------------------------------- /src/images/thumbar/win32/deafen-off-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GooseMod/OpenCore/HEAD/src/images/thumbar/win32/deafen-off-light.png -------------------------------------------------------------------------------- /src/images/thumbar/win32/disconnect-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GooseMod/OpenCore/HEAD/src/images/thumbar/win32/disconnect-light.png -------------------------------------------------------------------------------- /src/images/thumbar/win32/mute-off-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GooseMod/OpenCore/HEAD/src/images/thumbar/win32/mute-off-light.png -------------------------------------------------------------------------------- /src/images/thumbar/win32/video-off-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GooseMod/OpenCore/HEAD/src/images/thumbar/win32/video-off-light.png -------------------------------------------------------------------------------- /src/bootstrapModules.js: -------------------------------------------------------------------------------- 1 | exports.init = (bootstrapModules) => { 2 | for (const mod of Object.keys(bootstrapModules)) { 3 | exports[mod] = bootstrapModules[mod]; 4 | } 5 | }; -------------------------------------------------------------------------------- /test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo "Packing asar..." 4 | asar pack src core.asar 5 | 6 | echo "Copying asar..." 7 | cp core.asar ~/.config/discordcanary/0.0.131/modules/discord_desktop_core/core.asar 8 | 9 | echo "Running discord..." 10 | echo "" 11 | 12 | discord-canary -------------------------------------------------------------------------------- /src/ipcMain.js: -------------------------------------------------------------------------------- 1 | // This differs from app.asar's 2 | const { ipcMain } = require('electron'); 3 | 4 | const ipcNamespace = 'DISCORD_'; 5 | const getDiscordIPCEvent = (e) => e.startsWith(ipcNamespace) ? e : (ipcNamespace + e); 6 | 7 | 8 | module.exports = { 9 | on: (event, callback) => ipcMain.on(getDiscordIPCEvent(event), callback), 10 | removeListener: (event, callback) => ipcMain.removeListener(getDiscordIPCEvent(event), callback), 11 | reply: (event, channel, ...args) => event.sender.send(getDiscordIPCEvent(channel), ...args) 12 | }; -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | const bootstrapModules = require('./bootstrapModules'); 2 | 3 | let mainScreen; 4 | exports.startup = (bootstrap) => { 5 | if (!bootstrap.securityUtils) { // Not using OpenAsar, additional isn't passed 6 | // Some warning here in future 7 | return; 8 | } 9 | 10 | Constants = require('./Constants'); 11 | Constants.init(bootstrap.Constants); 12 | 13 | bootstrapModules.init(bootstrap); 14 | 15 | mainScreen = require('./mainScreen'); 16 | mainScreen.makeWindow(); 17 | }; 18 | 19 | exports.setMainWindowVisible = (visible) => mainScreen.setVisible(visible); -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | const { readFileSync, writeFileSync } = require('fs'); 2 | const { resolve, join } = require('path'); 3 | 4 | const paths = require('./utils/paths'); 5 | 6 | exports.exposeModuleResource = (asar, file) => { 7 | const asarPath = join(__dirname, asar, file); 8 | 9 | const nativePath = join(paths.getUserData(), file); 10 | writeFileSync(nativePath, readFileSync(asarPath)); 11 | 12 | return nativePath; 13 | }; 14 | 15 | const platform = process.platform; 16 | exports.platform = platform; 17 | 18 | exports.isWindows = platform.startsWith('win'); 19 | exports.isOSX = platform === 'darwin'; 20 | exports.isLinux = platform === 'linux'; -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 GooseMod 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/images/close.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | Slice 1 8 | Created with Sketch (http://www.bohemiancoding.com/sketch) 9 | 10 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/Constants.js: -------------------------------------------------------------------------------- 1 | // Largely copied as nothing to do 2 | exports.init = (bootstrap) => { 3 | const { APP_NAME, API_ENDPOINT, NEW_UPDATE_ENDPOINT, UPDATE_ENDPOINT, APP_ID } = bootstrap; // From bootstrap consts 4 | 5 | const DEFAULT_MAIN_WINDOW_ID = 0; 6 | const MAIN_APP_DIRNAME = __dirname; 7 | 8 | const UpdaterEvents = { 9 | UPDATE_NOT_AVAILABLE: 'UPDATE_NOT_AVAILABLE', 10 | CHECKING_FOR_UPDATES: 'CHECKING_FOR_UPDATES', 11 | UPDATE_ERROR: 'UPDATE_ERROR', 12 | UPDATE_MANUALLY: 'UPDATE_MANUALLY', 13 | UPDATE_AVAILABLE: 'UPDATE_AVAILABLE', 14 | MODULE_INSTALL_PROGRESS: 'MODULE_INSTALL_PROGRESS', 15 | UPDATE_DOWNLOADED: 'UPDATE_DOWNLOADED', 16 | MODULE_INSTALLED: 'MODULE_INSTALLED', 17 | CHECK_FOR_UPDATES: 'CHECK_FOR_UPDATES', 18 | QUIT_AND_INSTALL: 'QUIT_AND_INSTALL', 19 | MODULE_INSTALL: 'MODULE_INSTALL', 20 | MODULE_QUERY: 'MODULE_QUERY', 21 | UPDATER_HISTORY_QUERY_AND_TRUNCATE: 'UPDATER_HISTORY_QUERY_AND_TRUNCATE', 22 | UPDATER_HISTORY_RESPONSE: 'UPDATER_HISTORY_RESPONSE' 23 | }; 24 | const MenuEvents = { 25 | OPEN_HELP: 'menu:open-help', 26 | OPEN_SETTINGS: 'menu:open-settings', 27 | CHECK_FOR_UPDATES: 'menu:check-for-updates' 28 | }; 29 | 30 | module.exports = { 31 | APP_NAME, 32 | DEFAULT_MAIN_WINDOW_ID, 33 | MAIN_APP_DIRNAME, 34 | APP_ID, 35 | API_ENDPOINT, 36 | NEW_UPDATE_ENDPOINT, 37 | UPDATE_ENDPOINT, 38 | UpdaterEvents, 39 | MenuEvents 40 | }; 41 | }; -------------------------------------------------------------------------------- /src/appBadge.js: -------------------------------------------------------------------------------- 1 | const { BrowserWindow } = require('electron'); 2 | 3 | const utils = require('./utils'); 4 | const ipcMain = require('./ipcMain'); 5 | 6 | let hasInit = false; 7 | let lastIndex = null; 8 | let appIcons = []; 9 | 10 | exports.init = () => { 11 | if (process.platform !== 'win32') { // Electron only supports win.setOverlayIcon on Windows 12 | log('AppBadge', 'Refusing to init due to non-Windows'); 13 | return; 14 | } 15 | 16 | if (hasInit) { 17 | log('AppBadge', 'Already init, ignoring'); 18 | return; 19 | } 20 | 21 | exports.hasInit = hasInit = true; 22 | 23 | for (let i = 1; i <= 11; i++) { 24 | appIcons.push(utils.exposeModuleResource('images/badges', `badge-${i}.ico`)); 25 | } 26 | 27 | ipcMain.on('APP_BADGE_SET', (_e, num) => { 28 | const win = BrowserWindow.fromId(global.mainWindowId); 29 | 30 | let index, desc; 31 | 32 | switch (num) { 33 | case -1: 34 | index = 10; 35 | desc = 'Unread messages'; 36 | break; 37 | 38 | case 0: 39 | index = null; // Clear icon 40 | desc = 'No notifications'; 41 | break; 42 | 43 | default: 44 | index = Math.max(1, Math.min(num, 10)) - 1; // Clamp num between 1-10, -1 to make into 45 | desc = num + ' notifications'; 46 | break; 47 | } 48 | 49 | if (lastIndex !== index) { 50 | lastIndex = index; 51 | 52 | if (index === null) return win.setOverlayIcon(null, desc); 53 | 54 | win.setOverlayIcon(appIcons[index], desc); 55 | } 56 | }); 57 | }; -------------------------------------------------------------------------------- /src/images/discord.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 15 | 16 | 17 | 18 | 19 | 21 | 22 | 23 | 24 | 25 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /src/systemTray.js: -------------------------------------------------------------------------------- 1 | // Not modified *that* much as not much to change 2 | const { Tray, Menu, nativeImage } = require('electron'); 3 | 4 | const ipcMain = require('./ipcMain'); 5 | const securityUtils = require('./utils/securityUtils'); 6 | const Constants = require('./Constants'); 7 | const utils = require('./utils'); 8 | 9 | const TrayIconNames = { 10 | DEFAULT: 'tray', 11 | UNREAD: 'tray-unread', 12 | CONNECTED: 'tray-connected', 13 | SPEAKING: 'tray-speaking', 14 | MUTED: 'tray-muted', 15 | DEAFENED: 'tray-deafened' 16 | }; 17 | const MenuItems = { 18 | SECRET: 'SECRET', 19 | MUTE: 'MUTE', 20 | DEAFEN: 'DEAFEN', 21 | OPEN: 'OPEN', 22 | VOICE_SETTINGS: 'VOICE_SETTINGS', 23 | CHECK_UPDATE: 'CHECK_UPDATE', 24 | QUIT: 'QUIT', 25 | ACKNOWLEDGEMENTS: 'ACKNOWLEDGEMENTS' 26 | }; 27 | 28 | let trayIcons = {}; 29 | let menuItems = []; 30 | let applications = []; 31 | let contextMenu = []; 32 | let options, atomTray; 33 | 34 | 35 | exports.init = (_options) => { 36 | options = _options; 37 | 38 | const resourcePath = `images/systemtray`; 39 | 40 | for (const key of Object.keys(TrayIconNames)) { 41 | trayIcons[key] = utils.exposeModuleResource(resourcePath, `${TrayIconNames[key]}.png`); 42 | } 43 | 44 | currentIcon = trayIcons.DEFAULT; 45 | 46 | const { 47 | onToggleMute, 48 | onToggleDeafen, 49 | onTrayClicked, 50 | onOpenVoiceSettings, 51 | onCheckForUpdates 52 | } = options; 53 | const voiceConnected = currentIcon !== trayIcons.DEFAULT && currentIcon !== trayIcons.UNREAD; 54 | 55 | menuItems[MenuItems.SECRET] = { 56 | label: `Discord`, 57 | icon: trayIcons.DEFAULT, 58 | enabled: false 59 | }; 60 | menuItems[MenuItems.MUTE] = { 61 | label: `Mute`, 62 | type: 'checkbox', 63 | checked: currentIcon === trayIcons.MUTED || currentIcon === trayIcons.DEAFENED, 64 | visible: voiceConnected, 65 | click: onToggleMute 66 | }; 67 | menuItems[MenuItems.DEAFEN] = { 68 | label: `Deafen`, 69 | type: 'checkbox', 70 | checked: currentIcon === trayIcons.DEAFENED, 71 | visible: voiceConnected, 72 | click: onToggleDeafen 73 | }; 74 | menuItems[MenuItems.OPEN] = { 75 | label: `Open ${Constants.APP_NAME}`, 76 | type: 'normal', 77 | visible: process.platform === 'linux', 78 | click: onTrayClicked 79 | }; 80 | menuItems[MenuItems.VOICE_SETTINGS] = { 81 | label: 'Voice / Video Settings', 82 | type: 'normal', 83 | visible: voiceConnected, 84 | click: onOpenVoiceSettings 85 | }; 86 | menuItems[MenuItems.CHECK_UPDATE] = { 87 | label: 'Check for Updates...', 88 | type: 'normal', 89 | visible: process.platform !== 'darwin', 90 | click: onCheckForUpdates 91 | }; 92 | menuItems[MenuItems.QUIT] = { 93 | label: `Quit ${Constants.APP_NAME}`, 94 | role: 'quit' 95 | }; 96 | menuItems[MenuItems.ACKNOWLEDGEMENTS] = { 97 | label: 'Acknowledgements', 98 | type: 'normal', 99 | visible: process.platform !== 'darwin', 100 | click: () => securityUtils.saferShellOpenExternal('https://discord.com/acknowledgements') 101 | }; 102 | 103 | buildContext(); 104 | 105 | ipcMain.on('SYSTEM_TRAY_SET_ICON', (_e, icon) => setTrayIcon(icon)); 106 | ipcMain.on('SYSTEM_TRAY_SET_APPLICATIONS', (_e, newApplications) => setApplications(newApplications)); 107 | }; 108 | 109 | const buildContext = () => { 110 | const separator = { 111 | type: 'separator' 112 | }; 113 | 114 | const hasApplications = applications != null && applications.length > 0; 115 | contextMenu = [menuItems[MenuItems.SECRET], separator, ...(hasApplications ? [...applications, separator] : []), menuItems[MenuItems.OPEN], menuItems[MenuItems.MUTE], menuItems[MenuItems.DEAFEN], menuItems[MenuItems.VOICE_SETTINGS], menuItems[MenuItems.CHECK_UPDATE], menuItems[MenuItems.ACKNOWLEDGEMENTS], separator, menuItems[MenuItems.QUIT]]; 116 | }; 117 | 118 | const launchApplication = (id) => options.onLaunchApplication(id); 119 | 120 | function setApplications(newApplications) { 121 | applications = newApplications.map(application => ({ 122 | type: 'normal', 123 | label: application.name, 124 | click: () => launchApplication(application.id) 125 | })); 126 | 127 | buildContext(); 128 | setContext(); 129 | } 130 | 131 | const setContext = () => atomTray && atomTray.setContextMenu(Menu.buildFromTemplate(contextMenu)); 132 | 133 | const show = () => { 134 | if (atomTray != null) return; 135 | atomTray = new Tray(nativeImage.createFromPath(currentIcon)); // Initialize with last set icon 136 | 137 | atomTray.setToolTip(Constants.APP_NAME); // Set tray context menu 138 | setContext(); // Set Tray click behavior 139 | 140 | atomTray.on('click', options.onTrayClicked); 141 | }; 142 | exports.show = show; 143 | 144 | const hide = () => { 145 | if (atomTray == null) return; 146 | 147 | atomTray.destroy(); 148 | atomTray = null; 149 | }; 150 | 151 | const setTrayIcon = (icon) => { 152 | currentIcon = trayIcons[icon]; 153 | 154 | if (icon == null) return hide(); 155 | show(); 156 | 157 | const muteIndex = contextMenu.indexOf(menuItems[MenuItems.MUTE]); 158 | const deafenIndex = contextMenu.indexOf(menuItems[MenuItems.DEAFEN]); 159 | const voiceConnected = contextMenu[muteIndex].visible; 160 | let shouldSetContextMenu = false; 161 | 162 | if (currentIcon !== trayIcons.DEFAULT && currentIcon !== trayIcons.UNREAD) { 163 | // Show mute/deaf controls 164 | if (!voiceConnected) { 165 | contextMenu[muteIndex].visible = true; 166 | contextMenu[deafenIndex].visible = true; 167 | shouldSetContextMenu = true; 168 | } 169 | 170 | if (currentIcon === trayIcons.DEAFENED) { 171 | contextMenu[muteIndex].checked = true; 172 | contextMenu[deafenIndex].checked = true; 173 | shouldSetContextMenu = true; 174 | } else if (currentIcon === trayIcons.MUTED) { 175 | contextMenu[muteIndex].checked = true; 176 | contextMenu[deafenIndex].checked = false; 177 | shouldSetContextMenu = true; 178 | } else if (contextMenu[muteIndex].checked || contextMenu[deafenIndex].checked) { 179 | contextMenu[muteIndex].checked = false; 180 | contextMenu[deafenIndex].checked = false; 181 | shouldSetContextMenu = true; 182 | } 183 | } else if (voiceConnected) { 184 | contextMenu[muteIndex].visible = false; 185 | contextMenu[deafenIndex].visible = false; 186 | shouldSetContextMenu = true; 187 | } 188 | 189 | shouldSetContextMenu && setContextMenu(); 190 | return atomTray != null && atomTray.setImage(nativeImage.createFromPath(currentIcon)); 191 | } -------------------------------------------------------------------------------- /src/mainScreen.js: -------------------------------------------------------------------------------- 1 | const { BrowserWindow, screen, app } = require('electron'); 2 | 3 | const appSettings = require('./appSettings'); 4 | const buildInfo = require('./utils/buildInfo'); 5 | const splashScreen = require('./splashScreen'); 6 | const securityUtils = require('./utils/securityUtils'); 7 | const systemTray = require('./systemTray'); 8 | const appBadge = require('./appBadge'); 9 | 10 | const settings = appSettings.getSettings(); 11 | 12 | let mainWindow, mainWindowId; 13 | 14 | 15 | const MIN_WIDTH = settings.get('MIN_WIDTH', 940); 16 | const MIN_HEIGHT = settings.get('MIN_HEIGHT', 500); 17 | const MIN_VISIBLE_ON_SCREEN = 32; 18 | const DEFAULT_WIDTH = 1280; 19 | const DEFAULT_HEIGHT = 720; 20 | const DISCORD_NAMESPACE = 'DISCORD_'; 21 | 22 | function doAABBsOverlap(a, b) { // Copied from Discord because a 23 | const ax1 = a.x + a.width; 24 | const bx1 = b.x + b.width; 25 | const ay1 = a.y + a.height; 26 | const by1 = b.y + b.height; // clamp a to b, see if it is non-empty 27 | 28 | const cx0 = a.x < b.x ? b.x : a.x; 29 | const cx1 = ax1 < bx1 ? ax1 : bx1; 30 | 31 | if (cx1 - cx0 > 0) { 32 | const cy0 = a.y < b.y ? b.y : a.y; 33 | const cy1 = ay1 < by1 ? ay1 : by1; 34 | 35 | if (cy1 - cy0 > 0) { 36 | return true; 37 | } 38 | } 39 | 40 | return false; 41 | } 42 | 43 | const genEndpoint = () => { 44 | const fromConfig = process.env.DISCORD_WEBAPP_ENDPOINT || settings.get('WEBAPP_ENDPOINT'); 45 | if (fromConfig) return fromConfig; 46 | 47 | switch (buildInfo.releaseChannel) { 48 | case 'stable': 49 | return 'https://discord.com'; 50 | 51 | case 'development': 52 | return 'https://canary.discord.com'; 53 | 54 | default: 55 | return 'https://' + buildInfo.releaseChannel + '.discord.com'; 56 | } 57 | }; 58 | 59 | const WEBAPP_ENDPOINT = genEndpoint(); 60 | const WEBAPP_PATH = settings.get('WEBAPP_PATH', `/app?_=${Date.now()}`); 61 | const URL_TO_LOAD = `${WEBAPP_ENDPOINT}${WEBAPP_PATH}`; 62 | 63 | exports.makeWindow = (isVisible = false) => { 64 | const windowOptions = { 65 | title: 'Discord', 66 | backgroundColor: settings.get('BACKGROUND_COLOR', '#2f3136'), 67 | width: DEFAULT_WIDTH, 68 | height: DEFAULT_HEIGHT, 69 | minWidth: MIN_WIDTH, 70 | minHeight: MIN_HEIGHT, 71 | transparent: false, 72 | frame: false, 73 | resizable: true, 74 | show: isVisible, 75 | webPreferences: { 76 | blinkFeatures: 'EnumerateDevices,AudioOutputDevices', 77 | nodeIntegration: false, 78 | // preload: _path.default.join(__dirname, 'mainScreenPreload.js'), 79 | nativeWindowOpen: true, 80 | enableRemoteModule: false, 81 | spellcheck: true, 82 | contextIsolation: true, 83 | // NB: this is required in order to give popouts (or any child window opened via window.open w/ nativeWindowOpen) 84 | // a chance at a node environment (i.e. they run the preload, have an isolated context, etc.) when 85 | // `app.allowRendererProcessReuse === false` (default in Electron 7). 86 | additionalArguments: ['--enable-node-leakage-in-renderers'] 87 | } 88 | }; 89 | 90 | const savedBounds = settings.get('WINDOW_BOUNDS'); 91 | if (savedBounds) { 92 | savedBounds.width = Math.max(MIN_WIDTH, savedBounds.width); 93 | savedBounds.height = Math.max(MIN_HEIGHT, savedBounds.height); 94 | 95 | const displays = screen.getAllDisplays(); 96 | 97 | const display = displays.find(display => { 98 | const displayBound = display.workArea; 99 | 100 | displayBound.x += MIN_VISIBLE_ON_SCREEN; 101 | displayBound.y += MIN_VISIBLE_ON_SCREEN; 102 | displayBound.width -= 2 * MIN_VISIBLE_ON_SCREEN; 103 | displayBound.height -= 2 * MIN_VISIBLE_ON_SCREEN; 104 | 105 | return doAABBsOverlap(savedBounds, displayBound); 106 | }); 107 | 108 | if (display) { 109 | windowOptions.width = savedBounds.width; 110 | windowOptions.height = savedBounds.height; 111 | windowOptions.x = savedBounds.x; 112 | windowOptions.y = savedBounds.y; 113 | } else { 114 | windowOptions.center = true; 115 | } 116 | } else { 117 | windowOptions.center = true; 118 | } 119 | 120 | mainWindow = new BrowserWindow(windowOptions); 121 | 122 | mainWindowId = mainWindow.id; 123 | global.mainWindowId = mainWindowId; 124 | 125 | mainWindow.setMenuBarVisibility(false); 126 | 127 | // Event listeners oh no 128 | mainWindow.webContents.on('did-finish-load', () => { 129 | splashScreen.pageReady(); 130 | }); 131 | 132 | mainWindow.webContents.on('will-navigate', (e, url) => { 133 | if (securityUtils.checkUrlOriginMatches(url, WEBAPP_ENDPOINT)) e.preventDefault(); 134 | }); 135 | 136 | mainWindow.webContents.on('new-window', (e, url, name, _disposition, options) => { 137 | e.preventDefault(); 138 | 139 | if (name.startsWith(DISCORD_NAMESPACE) && securityUtils.checkUrlOriginMatches(url, WEBAPP_ENDPOINT) && (new URL(url)).pathname === '/popout') { 140 | popoutWindows.openOrFocusWindow(e, url, name, options, WEBAPP_ENDPOINT); 141 | return; 142 | } 143 | 144 | securityUtils.saferShellOpenExternal(url).catch(err => { 145 | log('MainScreen', 'Error opening external url', url, err); 146 | }); 147 | }); 148 | 149 | systemTray.init({ 150 | onCheckForUpdates: () => { 151 | const updater = _updater.default === null || _updater.default === void 0 ? void 0 : _updater.default.getUpdater(); 152 | 153 | if (updater != null) { 154 | checkForUpdatesWithUpdater(updater); 155 | } else { 156 | legacyModuleUpdater.checkForUpdates(); 157 | } 158 | }, 159 | 160 | onTrayClicked: () => setWindowVisible(true, true), 161 | 162 | onOpenVoiceSettings: () => {}, // Stubs because no DiscordNative 163 | onToggleMute: () => {}, 164 | onToggleDeafen: () => {}, 165 | onLaunchApplication: () => {} 166 | }); 167 | 168 | appBadge.init(); 169 | 170 | if (process.platform === 'linux' || process.platform === 'win32') { 171 | systemTray.show(); 172 | 173 | mainWindow.on('close', e => { 174 | if (mainWindow === null) { // Killed 175 | popoutWindows.closePopouts(); 176 | return; 177 | } 178 | 179 | // saveWindowConfig(mainWindow); // Quit app if that's the setting 180 | 181 | if (!settings.get('MINIMIZE_TO_TRAY', true)) return app.quit(); // Quit if disabled to minimize to tray, else minimize 182 | 183 | setWindowVisible(false); 184 | e.preventDefault(); 185 | }); 186 | } 187 | 188 | mainWindow.loadURL(URL_TO_LOAD); 189 | }; 190 | 191 | const setWindowVisible = (visible, unmin = false) => { 192 | if (!mainWindow) return; 193 | 194 | if (visible) { 195 | if (unmin || !mainWindow.isMinimized()) { 196 | mainWindow.show(); 197 | } 198 | } else { 199 | mainWindow.hide(); 200 | } 201 | 202 | mainWindow.setSkipTaskbar(!visible); 203 | }; 204 | 205 | exports.setVisible = (visible) => setWindowVisible(visible); --------------------------------------------------------------------------------