this repo has no description
1import { app, nativeTheme } from "electron"; 2import * as path from "node:path"; 3import * as fs from "node:fs/promises"; 4import * as fsSync from "node:fs"; 5import { parseTarGzip } from "nanotar"; 6 7const logger = moonlightHost.getLogger("nativeFixes/host"); 8const enabledFeatures = app.commandLine.getSwitchValue("enable-features").split(","); 9 10moonlightHost.events.on("window-created", function (browserWindow) { 11 if (moonlightHost.getConfigOption<boolean>("nativeFixes", "devtoolsThemeFix") ?? true) { 12 browserWindow.webContents.on("devtools-opened", () => { 13 if (!nativeTheme.shouldUseDarkColors) return; 14 nativeTheme.themeSource = "light"; 15 setTimeout(() => { 16 nativeTheme.themeSource = "dark"; 17 }, 100); 18 }); 19 } 20}); 21 22if (moonlightHost.getConfigOption<boolean>("nativeFixes", "disableRendererBackgrounding") ?? true) { 23 // Discord already disables UseEcoQoSForBackgroundProcess and some other 24 // related features 25 app.commandLine.appendSwitch("disable-renderer-backgrounding"); 26 app.commandLine.appendSwitch("disable-backgrounding-occluded-windows"); 27 28 // already added on Windows, but not on other operating systems 29 app.commandLine.appendSwitch("disable-background-timer-throttling"); 30} 31 32if (moonlightHost.getConfigOption<boolean>("nativeFixes", "vulkan") ?? false) { 33 enabledFeatures.push("Vulkan", "DefaultANGLEVulkan", "VulkanFromANGLE"); 34} 35 36if (process.platform === "linux") { 37 if (moonlightHost.getConfigOption<boolean>("nativeFixes", "linuxAutoscroll") ?? false) { 38 app.commandLine.appendSwitch("enable-blink-features", "MiddleClickAutoscroll"); 39 } 40 41 if (moonlightHost.getConfigOption<boolean>("nativeFixes", "linuxSpeechDispatcher") ?? true) { 42 app.commandLine.appendSwitch("enable-speech-dispatcher"); 43 } 44 45 if (moonlightHost.getConfigOption<boolean>("nativeFixes", "linuxHevcSupport") ?? true) { 46 enabledFeatures.push("PlatformHEVCDecoderSupport"); 47 } 48} 49 50// NOTE: Only tested if this appears on Windows, it should appear on all when 51// hardware acceleration is disabled 52const noAccel = app.commandLine.hasSwitch("disable-gpu-compositing"); 53if ((moonlightHost.getConfigOption<boolean>("nativeFixes", "vaapi") ?? true) && !noAccel) { 54 if (process.platform === "linux") { 55 // These will eventually be renamed https://source.chromium.org/chromium/chromium/src/+/5482210941a94d70406b8da962426e4faca7fce4 56 enabledFeatures.push("VaapiVideoEncoder", "VaapiVideoDecoder", "VaapiVideoDecodeLinuxGL"); 57 58 if (moonlightHost.getConfigOption<boolean>("nativeFixes", "vaapiIgnoreDriverChecks") ?? false) 59 enabledFeatures.push("VaapiIgnoreDriverChecks"); 60 } 61} 62 63app.commandLine.appendSwitch("enable-features", [...new Set(enabledFeatures)].join(",")); 64 65if (process.platform === "linux" && moonlightHost.getConfigOption<boolean>("nativeFixes", "linuxUpdater")) { 66 const exePath = app.getPath("exe"); 67 const appName = path.basename(exePath); 68 const targetDir = path.dirname(exePath); 69 const { releaseChannel }: { releaseChannel: string } = JSON.parse( 70 fsSync.readFileSync(path.join(targetDir, "resources", "build_info.json"), "utf8") 71 ); 72 73 const updaterModule = require(path.join(moonlightHost.asarPath, "app_bootstrap", "hostUpdater.js")); 74 const updater = updaterModule.constructor; 75 76 async function doUpdate(cb: (percent: number) => void) { 77 logger.debug("Extracting to", targetDir); 78 79 const exists = (path: string) => 80 fs 81 .stat(path) 82 .then(() => true) 83 .catch(() => false); 84 85 const url = `https://discord.com/api/download/${releaseChannel}?platform=linux&format=tar.gz`; 86 const resp = await fetch(url, { 87 cache: "no-store" 88 }); 89 90 const reader = resp.body!.getReader(); 91 const contentLength = parseInt(resp.headers.get("Content-Length") ?? "0"); 92 logger.info(`Expecting ${contentLength} bytes for the update`); 93 const bytes = new Uint8Array(contentLength); 94 let pos = 0; 95 let lastPercent = 0; 96 97 while (true) { 98 const { done, value } = await reader.read(); 99 if (done) { 100 break; 101 } else { 102 bytes.set(value, pos); 103 pos += value.length; 104 105 const newPercent = Math.floor((pos / contentLength) * 100); 106 if (lastPercent !== newPercent) { 107 lastPercent = newPercent; 108 cb(newPercent); 109 } 110 } 111 } 112 113 const files = await parseTarGzip(bytes); 114 115 for (const file of files) { 116 if (!file.data) continue; 117 // @ts-expect-error What do you mean their own types are wrong 118 if (file.type !== "file") continue; 119 120 // Discord update files are inside of a main "Discord(PTB|Canary)" folder 121 const filePath = file.name.replace(`${appName}/`, ""); 122 logger.info("Extracting", filePath); 123 124 let targetFilePath = path.join(targetDir, filePath); 125 if (filePath === "resources/app.asar") { 126 // You tried 127 targetFilePath = path.join(targetDir, "resources", "_app.asar"); 128 } else if (filePath === appName || filePath === "chrome_crashpad_handler") { 129 // Can't write over the executable? Just move it! 4head 130 if (await exists(targetFilePath)) { 131 await fs.rename(targetFilePath, targetFilePath + ".bak"); 132 await fs.unlink(targetFilePath + ".bak"); 133 } 134 } 135 const targetFileDir = path.dirname(targetFilePath); 136 137 if (!(await exists(targetFileDir))) await fs.mkdir(targetFileDir, { recursive: true }); 138 await fs.writeFile(targetFilePath, file.data); 139 140 const mode = file.attrs?.mode; 141 if (mode != null) { 142 // Not sure why this slice is needed 143 await fs.chmod(targetFilePath, mode.slice(-3)); 144 } 145 } 146 147 logger.debug("Done updating"); 148 } 149 150 const realEmit = updater.prototype.emit; 151 updater.prototype.emit = function (event: string, ...args: any[]) { 152 // Arrow functions don't bind `this` :D 153 const call = (event: string, ...args: any[]) => realEmit.call(this, event, ...args); 154 155 if (event === "update-manually") { 156 const latestVerStr: string = args[0]; 157 logger.debug("update-manually called, intercepting", latestVerStr); 158 call("update-available"); 159 160 (async () => { 161 try { 162 await doUpdate((progress) => { 163 call("update-progress", progress); 164 }); 165 // Copied from the win32 updater 166 this.updateVersion = latestVerStr; 167 call( 168 "update-downloaded", 169 {}, 170 releaseChannel, 171 latestVerStr, 172 new Date(), 173 this.updateUrl, 174 this.quitAndInstall.bind(this) 175 ); 176 } catch (e) { 177 logger.error("Error updating", e); 178 } 179 })(); 180 181 return this; 182 } else { 183 return realEmit.call(this, event, ...args); 184 } 185 }; 186}