Merge branch 'main' into isMaximized-in-renderer
This commit is contained in:
		
						commit
						7ed49726f8
					
				
					 16 changed files with 454 additions and 163 deletions
				
			
		|  | @ -24,10 +24,10 @@ | |||
|         "updateMeta": "tsx scripts/utils/updateMeta.mts" | ||||
|     }, | ||||
|     "dependencies": { | ||||
|         "arrpc": "github:OpenAsar/arrpc#98879cae0565e6fce34e4cb6f544bf42c6a7e7c8" | ||||
|         "arrpc": "github:OpenAsar/arrpc#6960a8fd4d65d566da93dbdb8a7ca474aa0a3c9c" | ||||
|     }, | ||||
|     "optionalDependencies": { | ||||
|         "@vencord/venmic": "^3.3.2" | ||||
|         "@vencord/venmic": "^3.4.2" | ||||
|     }, | ||||
|     "devDependencies": { | ||||
|         "@fal-works/esbuild-plugin-global-externals": "^2.1.2", | ||||
|  |  | |||
|  | @ -6,13 +6,13 @@ settings: | |||
| 
 | ||||
| dependencies: | ||||
|   arrpc: | ||||
|     specifier: github:OpenAsar/arrpc#98879cae0565e6fce34e4cb6f544bf42c6a7e7c8 | ||||
|     version: github.com/OpenAsar/arrpc/98879cae0565e6fce34e4cb6f544bf42c6a7e7c8 | ||||
|     specifier: github:OpenAsar/arrpc#6960a8fd4d65d566da93dbdb8a7ca474aa0a3c9c | ||||
|     version: github.com/OpenAsar/arrpc/6960a8fd4d65d566da93dbdb8a7ca474aa0a3c9c | ||||
| 
 | ||||
| optionalDependencies: | ||||
|   '@vencord/venmic': | ||||
|     specifier: ^3.3.2 | ||||
|     version: 3.3.2 | ||||
|     specifier: ^3.4.2 | ||||
|     version: 3.4.2 | ||||
| 
 | ||||
| devDependencies: | ||||
|   '@fal-works/esbuild-plugin-global-externals': | ||||
|  | @ -1000,14 +1000,14 @@ packages: | |||
|       type-fest: 3.13.1 | ||||
|     dev: true | ||||
| 
 | ||||
|   /@vencord/venmic@3.3.2: | ||||
|     resolution: {integrity: sha512-fwGr5v+Xe4EisKxxhTDlUDamBGlBzSK3+yYZH/7d9ui1j8Q78wCbE1iP8MnDjBvV2kIKn/xV/84wjt9iQvDkFw==} | ||||
|   /@vencord/venmic@3.4.2: | ||||
|     resolution: {integrity: sha512-nwGjarof1wVvecGksGONfb+PduhY4gSuHTm39LktiQayHS69+yv4lepq4k1lxZzjOgFTy5VXfmJqxt+BpqoUUQ==} | ||||
|     engines: {node: '>=14.15'} | ||||
|     os: [linux] | ||||
|     requiresBuild: true | ||||
|     dependencies: | ||||
|       cmake-js: 7.3.0 | ||||
|       node-addon-api: 7.1.0 | ||||
|       node-addon-api: 8.0.0 | ||||
|       pkg-prebuilds: 0.2.1 | ||||
|     transitivePeerDependencies: | ||||
|       - supports-color | ||||
|  | @ -1333,11 +1333,11 @@ packages: | |||
|       possible-typed-array-names: 1.0.0 | ||||
|     dev: true | ||||
| 
 | ||||
|   /axios@1.6.7(debug@4.3.4): | ||||
|     resolution: {integrity: sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA==} | ||||
|   /axios@1.6.8(debug@4.3.4): | ||||
|     resolution: {integrity: sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==} | ||||
|     requiresBuild: true | ||||
|     dependencies: | ||||
|       follow-redirects: 1.15.5(debug@4.3.4) | ||||
|       follow-redirects: 1.15.6(debug@4.3.4) | ||||
|       form-data: 4.0.0 | ||||
|       proxy-from-env: 1.1.0 | ||||
|     transitivePeerDependencies: | ||||
|  | @ -1576,7 +1576,7 @@ packages: | |||
|     hasBin: true | ||||
|     requiresBuild: true | ||||
|     dependencies: | ||||
|       axios: 1.6.7(debug@4.3.4) | ||||
|       axios: 1.6.8(debug@4.3.4) | ||||
|       debug: 4.3.4 | ||||
|       fs-extra: 11.2.0 | ||||
|       lodash.isplainobject: 4.0.6 | ||||
|  | @ -2540,8 +2540,8 @@ packages: | |||
|     resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==} | ||||
|     dev: true | ||||
| 
 | ||||
|   /follow-redirects@1.15.5(debug@4.3.4): | ||||
|     resolution: {integrity: sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==} | ||||
|   /follow-redirects@1.15.6(debug@4.3.4): | ||||
|     resolution: {integrity: sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==} | ||||
|     engines: {node: '>=4.0'} | ||||
|     requiresBuild: true | ||||
|     peerDependencies: | ||||
|  | @ -3581,9 +3581,9 @@ packages: | |||
|     dev: true | ||||
|     optional: true | ||||
| 
 | ||||
|   /node-addon-api@7.1.0: | ||||
|     resolution: {integrity: sha512-mNcltoe1R8o7STTegSOHdnJNN7s5EUvhoS7ShnTHDyOSd+8H+UdWODq6qSv67PjC8Zc5JRT8+oLAMCr0SIXw7g==} | ||||
|     engines: {node: ^16 || ^18 || >= 20} | ||||
|   /node-addon-api@8.0.0: | ||||
|     resolution: {integrity: sha512-ipO7rsHEBqa9STO5C5T10fj732ml+5kLN1cAG8/jdHd56ldQeGj3Q7+scUS+VHK/qy1zLEwC4wMK5+yM0btPvw==} | ||||
|     engines: {node: ^18 || ^20 || >= 21} | ||||
|     requiresBuild: true | ||||
|     dev: false | ||||
|     optional: true | ||||
|  | @ -4783,10 +4783,10 @@ packages: | |||
|       readable-stream: 3.6.2 | ||||
|     dev: true | ||||
| 
 | ||||
|   github.com/OpenAsar/arrpc/98879cae0565e6fce34e4cb6f544bf42c6a7e7c8: | ||||
|     resolution: {tarball: https://codeload.github.com/OpenAsar/arrpc/tar.gz/98879cae0565e6fce34e4cb6f544bf42c6a7e7c8} | ||||
|   github.com/OpenAsar/arrpc/6960a8fd4d65d566da93dbdb8a7ca474aa0a3c9c: | ||||
|     resolution: {tarball: https://codeload.github.com/OpenAsar/arrpc/tar.gz/6960a8fd4d65d566da93dbdb8a7ca474aa0a3c9c} | ||||
|     name: arrpc | ||||
|     version: 3.2.0 | ||||
|     version: 3.3.1 | ||||
|     hasBin: true | ||||
|     dependencies: | ||||
|       ws: 8.13.0 | ||||
|  |  | |||
|  | @ -27,14 +27,18 @@ process.env.VENCORD_USER_DATA_DIR = DATA_DIR; | |||
| function init() { | ||||
|     const { disableSmoothScroll, hardwareAcceleration } = Settings.store; | ||||
| 
 | ||||
|     if (hardwareAcceleration === false) app.disableHardwareAcceleration(); | ||||
|     if (hardwareAcceleration === false) { | ||||
|         app.disableHardwareAcceleration(); | ||||
|     } else { | ||||
|         app.commandLine.appendSwitch("enable-features", "VaapiVideoDecodeLinuxGL,VaapiVideoEncoder,VaapiVideoDecoder"); | ||||
|     } | ||||
| 
 | ||||
|     if (disableSmoothScroll) { | ||||
|         app.commandLine.appendSwitch("disable-smooth-scrolling"); | ||||
|     } | ||||
| 
 | ||||
|     // 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.
 | ||||
|     //
 | ||||
|  |  | |||
|  | @ -73,6 +73,10 @@ const [addSettingsListener, removeSettingsListeners] = makeSettingsListenerHelpe | |||
| 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", | ||||
|  | @ -120,7 +124,7 @@ function initTray(win: BrowserWindow) { | |||
|     tray = new Tray(ICON_PATH); | ||||
|     tray.setToolTip("Vesktop"); | ||||
|     tray.setContextMenu(trayMenu); | ||||
|     tray.on("click", () => win.show()); | ||||
|     tray.on("click", onTrayClick); | ||||
| } | ||||
| 
 | ||||
| async function clearData(win: BrowserWindow) { | ||||
|  |  | |||
|  | @ -4,17 +4,58 @@ | |||
|  * Copyright (c) 2023 Vendicated and Vencord contributors | ||||
|  */ | ||||
| 
 | ||||
| import type { PatchBay } from "@vencord/venmic"; | ||||
| import type { 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"; | ||||
| 
 | ||||
| type LinkData = Parameters<PatchBay["link"]>[0]; | ||||
| type LinkData = Parameters<PatchBayType["link"]>[0]; | ||||
| 
 | ||||
| let PatchBay: typeof PatchBayType | undefined; | ||||
| let patchBayInstance: PatchBayType | undefined; | ||||
| 
 | ||||
| let imported = false; | ||||
| let initialized = false; | ||||
| let patchBay: import("@vencord/venmic").PatchBay | undefined; | ||||
| let isGlibcxxToOld = 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 ( | ||||
|  | @ -25,33 +66,17 @@ function getRendererAudioServicePid() { | |||
|     ); | ||||
| } | ||||
| 
 | ||||
| function obtainVenmic() { | ||||
|     if (!initialized) { | ||||
|         initialized = true; | ||||
|         try { | ||||
|             const { PatchBay } = require( | ||||
|                 join(STATIC_DIR, `dist/venmic-${process.arch}.node`) | ||||
|             ) as typeof import("@vencord/venmic"); | ||||
|             patchBay = new PatchBay(); | ||||
|         } catch (e: any) { | ||||
|             console.error("Failed to initialise venmic. Make sure you're using pipewire", e); | ||||
|             isGlibcxxToOld = (e?.stack || e?.message || "").toLowerCase().includes("glibc"); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     return patchBay; | ||||
| } | ||||
| 
 | ||||
| ipcMain.handle(IpcEvents.VIRT_MIC_LIST, () => { | ||||
|     const audioPid = getRendererAudioServicePid(); | ||||
| 
 | ||||
|     const list = obtainVenmic() | ||||
|         ?.list() | ||||
|         .filter(s => s["application.process.id"] !== audioPid) | ||||
|         .map(s => s["application.name"]); | ||||
| 
 | ||||
|     return list | ||||
|         ? { ok: true, targets: [...new Set(list)] } // Remove duplicates
 | ||||
|         : { ok: false, isGlibcxxToOld }; | ||||
|     const uniqueTargets = [...new Set(list)]; | ||||
| 
 | ||||
|     return list ? { ok: true, targets: uniqueTargets, hasPipewirePulse } : { ok: false, isGlibCxxOutdated }; | ||||
| }); | ||||
| 
 | ||||
| ipcMain.handle(IpcEvents.VIRT_MIC_START, (_, targets: string[], workaround?: boolean) => { | ||||
|  | @ -72,11 +97,12 @@ ipcMain.handle(IpcEvents.VIRT_MIC_START, (_, targets: string[], workaround?: boo | |||
|     return obtainVenmic()?.link(data); | ||||
| }); | ||||
| 
 | ||||
| ipcMain.handle(IpcEvents.VIRT_MIC_START_SYSTEM, (_, workaround?: boolean) => { | ||||
| ipcMain.handle(IpcEvents.VIRT_MIC_START_SYSTEM, (_, workaround?: boolean, onlyDefaultSpeakers?: boolean) => { | ||||
|     const pid = getRendererAudioServicePid(); | ||||
| 
 | ||||
|     const data: LinkData = { | ||||
|         exclude: [{ key: "application.process.id", value: pid }] | ||||
|         exclude: [{ key: "application.process.id", value: pid }], | ||||
|         only_default_speakers: onlyDefaultSpeakers | ||||
|     }; | ||||
| 
 | ||||
|     if (workaround) { | ||||
|  |  | |||
|  | @ -77,9 +77,12 @@ export const VesktopNative = { | |||
|     /** only available on Linux. */ | ||||
|     virtmic: { | ||||
|         list: () => | ||||
|             invoke<{ ok: false; isGlibcxxToOld: boolean } | { ok: true; targets: string[] }>(IpcEvents.VIRT_MIC_LIST), | ||||
|             invoke< | ||||
|                 { ok: false; isGlibCxxOutdated: boolean } | { ok: true; targets: string[]; hasPipewirePulse: boolean } | ||||
|             >(IpcEvents.VIRT_MIC_LIST), | ||||
|         start: (targets: string[], workaround?: boolean) => invoke<void>(IpcEvents.VIRT_MIC_START, targets, workaround), | ||||
|         startSystem: (workaround?: boolean) => invoke<void>(IpcEvents.VIRT_MIC_START_SYSTEM, workaround), | ||||
|         startSystem: (workaround?: boolean, onlyDefaultSpeakers?: boolean) => | ||||
|             invoke<void>(IpcEvents.VIRT_MIC_START_SYSTEM, workaround, onlyDefaultSpeakers), | ||||
|         stop: () => invoke<void>(IpcEvents.VIRT_MIC_STOP) | ||||
|     }, | ||||
|     arrpc: { | ||||
|  |  | |||
|  | @ -6,7 +6,7 @@ | |||
| 
 | ||||
| import "./screenSharePicker.css"; | ||||
| 
 | ||||
| import { closeModal, Margins, Modals, openModal, useAwaiter } from "@vencord/types/utils"; | ||||
| import { closeModal, Logger, Margins, Modals, ModalSize, openModal, useAwaiter } from "@vencord/types/utils"; | ||||
| import { findStoreLazy, onceReady } from "@vencord/types/webpack"; | ||||
| import { | ||||
|     Button, | ||||
|  | @ -36,7 +36,9 @@ interface StreamSettings { | |||
|     fps: StreamFps; | ||||
|     audio: boolean; | ||||
|     audioSource?: string; | ||||
|     contentHint?: string; | ||||
|     workaround?: boolean; | ||||
|     onlyDefaultSpeakers?: boolean; | ||||
| } | ||||
| 
 | ||||
| export interface StreamPick extends StreamSettings { | ||||
|  | @ -49,7 +51,9 @@ interface Source { | |||
|     url: string; | ||||
| } | ||||
| 
 | ||||
| let currentSettings: StreamSettings | null = null; | ||||
| export let currentSettings: StreamSettings | null = null; | ||||
| 
 | ||||
| const logger = new Logger("VesktopScreenShare"); | ||||
| 
 | ||||
| addPatch({ | ||||
|     patches: [ | ||||
|  | @ -59,6 +63,20 @@ addPatch({ | |||
|                 match: /this.localWant=/, | ||||
|                 replace: "$self.patchStreamQuality(this);$&" | ||||
|             } | ||||
|         }, | ||||
|         { | ||||
|             find: "x-google-max-bitrate", | ||||
|             replacement: [ | ||||
|                 { | ||||
|                     // eslint-disable-next-line no-useless-escape
 | ||||
|                     match: /"x-google-max-bitrate=".concat\(\i\)/, | ||||
|                     replace: '"x-google-max-bitrate=".concat("80_000")' | ||||
|                 }, | ||||
|                 { | ||||
|                     match: /;level-asymmetry-allowed=1/, | ||||
|                     replace: ";b=AS:800000;level-asymmetry-allowed=1" | ||||
|                 } | ||||
|             ] | ||||
|         } | ||||
|     ], | ||||
|     patchStreamQuality(opts: any) { | ||||
|  | @ -73,6 +91,14 @@ addPatch({ | |||
|             bitrateMax: 8000000, | ||||
|             bitrateTarget: 600000 | ||||
|         }); | ||||
|         if (opts?.encode) { | ||||
|             Object.assign(opts.encode, { | ||||
|                 framerate, | ||||
|                 width, | ||||
|                 height, | ||||
|                 pixelCount: height * width | ||||
|             }); | ||||
|         } | ||||
|         Object.assign(opts.capture, { | ||||
|             framerate, | ||||
|             width, | ||||
|  | @ -167,54 +193,102 @@ function StreamSettings({ | |||
|     ); | ||||
| 
 | ||||
|     return ( | ||||
|         <div> | ||||
|             <Forms.FormTitle>What you're streaming</Forms.FormTitle> | ||||
|             <Card className="vcd-screen-picker-card vcd-screen-picker-preview"> | ||||
|                 <img src={thumb} alt="" /> | ||||
|                 <Text variant="text-sm/normal">{source.name}</Text> | ||||
|             </Card> | ||||
|         <div className="vcd-screen-picker-settings-grid"> | ||||
|             <div> | ||||
|                 <Forms.FormTitle>What you're streaming</Forms.FormTitle> | ||||
|                 <Card className="vcd-screen-picker-card vcd-screen-picker-preview"> | ||||
|                     <img src={thumb} alt="" /> | ||||
|                     <Text variant="text-sm/normal">{source.name}</Text> | ||||
|                 </Card> | ||||
| 
 | ||||
|             <Forms.FormTitle>Stream Settings</Forms.FormTitle> | ||||
|                 <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> | ||||
|                 <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> | ||||
|                         <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> | ||||
|                         </section> | ||||
|                     </div> | ||||
|                 </Card> | ||||
|             </div> | ||||
| 
 | ||||
|             <div> | ||||
|                 {isWindows && ( | ||||
|                     <Switch | ||||
|                         value={settings.audio} | ||||
|  | @ -230,11 +304,13 @@ function StreamSettings({ | |||
|                     <AudioSourcePickerLinux | ||||
|                         audioSource={settings.audioSource} | ||||
|                         workaround={settings.workaround} | ||||
|                         onlyDefaultSpeakers={settings.onlyDefaultSpeakers} | ||||
|                         setAudioSource={source => setSettings(s => ({ ...s, audioSource: source }))} | ||||
|                         setWorkaround={workaround => setSettings(s => ({ ...s, workaround: workaround }))} | ||||
|                         setWorkaround={value => setSettings(s => ({ ...s, workaround: value }))} | ||||
|                         setOnlyDefaultSpeakers={value => setSettings(s => ({ ...s, onlyDefaultSpeakers: value }))} | ||||
|                     /> | ||||
|                 )} | ||||
|             </Card> | ||||
|             </div> | ||||
|         </div> | ||||
|     ); | ||||
| } | ||||
|  | @ -242,63 +318,102 @@ function StreamSettings({ | |||
| function AudioSourcePickerLinux({ | ||||
|     audioSource, | ||||
|     workaround, | ||||
|     onlyDefaultSpeakers, | ||||
|     setAudioSource, | ||||
|     setWorkaround | ||||
|     setWorkaround, | ||||
|     setOnlyDefaultSpeakers | ||||
| }: { | ||||
|     audioSource?: string; | ||||
|     workaround?: boolean; | ||||
|     onlyDefaultSpeakers?: boolean; | ||||
|     setAudioSource(s: string): void; | ||||
|     setWorkaround(b: boolean): void; | ||||
|     setOnlyDefaultSpeakers(b: boolean): void; | ||||
| }) { | ||||
|     const [sources, _, loading] = useAwaiter(() => VesktopNative.virtmic.list(), { | ||||
|         fallbackValue: { ok: true, targets: [] } | ||||
|         fallbackValue: { ok: true, targets: [], hasPipewirePulse: true } | ||||
|     }); | ||||
| 
 | ||||
|     const allSources = sources.ok ? ["None", "Entire System", ...sources.targets] : null; | ||||
|     const hasPipewirePulse = sources.ok ? sources.hasPipewirePulse : true; | ||||
| 
 | ||||
|     const [ignorePulseWarning, setIgnorePulseWarning] = useState(false); | ||||
| 
 | ||||
|     return ( | ||||
|         <section> | ||||
|             <Forms.FormTitle>Audio</Forms.FormTitle> | ||||
|             {loading && <Forms.FormTitle>Loading Audio sources...</Forms.FormTitle>} | ||||
|             {!sources.ok && | ||||
|                 (sources.isGlibcxxToOld ? ( | ||||
|         <> | ||||
|             <Forms.FormTitle>Audio Settings</Forms.FormTitle> | ||||
|             <Card className="vcd-screen-picker-card"> | ||||
|                 {loading ? ( | ||||
|                     <Forms.FormTitle>Loading Audio Sources...</Forms.FormTitle> | ||||
|                 ) : ( | ||||
|                     <Forms.FormTitle>Audio Source</Forms.FormTitle> | ||||
|                 )} | ||||
| 
 | ||||
|                 {!sources.ok && sources.isGlibCxxOutdated && ( | ||||
|                     <Forms.FormText> | ||||
|                         Failed to retrieve Audio Sources because your C++ library is too old to run venmic. If you would | ||||
|                         like to stream with Audio, see{" "} | ||||
|                         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> | ||||
|                         </a>{" "} | ||||
|                         for possible solutions. | ||||
|                     </Forms.FormText> | ||||
|                 )} | ||||
| 
 | ||||
|                 {hasPipewirePulse || ignorePulseWarning ? ( | ||||
|                     allSources && ( | ||||
|                         <Select | ||||
|                             options={allSources.map(s => ({ label: s, value: s, default: s === "None" }))} | ||||
|                             isSelected={s => s === audioSource} | ||||
|                             select={setAudioSource} | ||||
|                             serialize={String} | ||||
|                         /> | ||||
|                     ) | ||||
|                 ) : ( | ||||
|                     <Forms.FormText> | ||||
|                         Failed to retrieve Audio Sources. If you would like to stream with Audio, make sure you're using | ||||
|                         Pipewire, not Pulseaudio | ||||
|                     </Forms.FormText> | ||||
|                 ))} | ||||
|                     <Text variant="text-sm/normal"> | ||||
|                         Could not find pipewire-pulse. This usually means that you do not run pipewire as your main | ||||
|                         audio-server. <br /> | ||||
|                         You can still continue, however, please beware that you can only share audio of apps that are | ||||
|                         running under pipewire. | ||||
|                         <br /> | ||||
|                         <a onClick={() => setIgnorePulseWarning(true)}>I know what I'm doing</a> | ||||
|                     </Text> | ||||
|                 )} | ||||
| 
 | ||||
|             {allSources && ( | ||||
|                 <Select | ||||
|                     options={allSources.map(s => ({ label: s, value: s, default: s === "None" }))} | ||||
|                     isSelected={s => s === audioSource} | ||||
|                     select={setAudioSource} | ||||
|                     serialize={String} | ||||
|                 /> | ||||
|             )} | ||||
|                 <Forms.FormDivider className={Margins.top16 + " " + Margins.bottom16} /> | ||||
| 
 | ||||
|             <Forms.FormDivider className={Margins.top16 + " " + Margins.bottom16} /> | ||||
|                 <Switch | ||||
|                     onChange={setWorkaround} | ||||
|                     value={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 | ||||
|                 onChange={setWorkaround} | ||||
|                 value={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> | ||||
|         </section> | ||||
|                 <Switch | ||||
|                     hideBorder | ||||
|                     onChange={setOnlyDefaultSpeakers} | ||||
|                     disabled={audioSource !== "Entire System"} | ||||
|                     value={onlyDefaultSpeakers ?? true} | ||||
|                     note={ | ||||
|                         <> | ||||
|                             When sharing entire desktop audio, only share apps that play to the default speakers and | ||||
|                             ignore apps that play to other speakers or devices. | ||||
|                         </> | ||||
|                     } | ||||
|                 > | ||||
|                     Only Default Speakers | ||||
|                 </Switch> | ||||
|             </Card> | ||||
|         </> | ||||
|     ); | ||||
| } | ||||
| 
 | ||||
|  | @ -319,16 +434,16 @@ function ModalComponent({ | |||
|     const [settings, setSettings] = useState<StreamSettings>({ | ||||
|         resolution: "1080", | ||||
|         fps: "60", | ||||
|         contentHint: "motion", | ||||
|         audio: true | ||||
|     }); | ||||
| 
 | ||||
|     return ( | ||||
|         <Modals.ModalRoot {...modalProps}> | ||||
|         <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} /> | ||||
|  | @ -341,35 +456,62 @@ function ModalComponent({ | |||
|                     /> | ||||
|                 )} | ||||
|             </Modals.ModalContent> | ||||
| 
 | ||||
|             <Modals.ModalFooter className="vcd-screen-picker-footer"> | ||||
|                 <Button | ||||
|                     disabled={!selected} | ||||
|                     onClick={() => { | ||||
|                         currentSettings = settings; | ||||
| 
 | ||||
|                         // If there are 2 connections, the second one is the existing stream.
 | ||||
|                         // In that case, we patch its quality
 | ||||
|                         const conn = [...MediaEngineStore.getMediaEngine().connections][1]; | ||||
|                         if (conn && conn.videoStreamParameters.length > 0) { | ||||
|                         try { | ||||
|                             const frameRate = Number(settings.fps); | ||||
|                             const height = Number(settings.resolution); | ||||
|                             const width = Math.round(height * (16 / 9)); | ||||
|                             Object.assign(conn.videoStreamParameters[0], { | ||||
|                                 maxFrameRate: Number(settings.fps), | ||||
|                                 maxPixelCount: width * height, | ||||
|                                 maxBitrate: 8000000, | ||||
|                                 maxResolution: { | ||||
|                                     type: "fixed", | ||||
|                                     width, | ||||
|                                     height | ||||
|                                 } | ||||
|                             }); | ||||
|                         } | ||||
| 
 | ||||
|                         submit({ | ||||
|                             id: selected!, | ||||
|                             ...settings | ||||
|                         }); | ||||
|                             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, | ||||
|                                     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(); | ||||
|                     }} | ||||
|  |  | |||
|  | @ -11,6 +11,21 @@ | |||
|     gap: 1em; | ||||
| } | ||||
| 
 | ||||
| .vcd-screen-picker-settings-grid { | ||||
|     gap: 1em; | ||||
|     display: grid; | ||||
|     grid-template-columns: 1fr 1fr; | ||||
| } | ||||
| 
 | ||||
| .vcd-screen-picker-settings-grid > div { | ||||
|     display: flex; | ||||
|     flex-direction: column; | ||||
| } | ||||
| 
 | ||||
| .vcd-screen-picker-card { | ||||
|     flex-grow: 1; | ||||
| } | ||||
| 
 | ||||
| .vcd-screen-picker-grid { | ||||
|     display: grid; | ||||
|     grid-template-columns: 1fr 1fr; | ||||
|  | @ -122,3 +137,10 @@ | |||
| .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; | ||||
| } | ||||
|  | @ -83,6 +83,12 @@ const SettingsOptions: Record<string, Array<BooleanSetting | SettingsComponent>> | |||
|             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", | ||||
|  |  | |||
|  | @ -3,3 +3,9 @@ | |||
| [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; | ||||
| } | ||||
|  | @ -4,7 +4,7 @@ | |||
|  * Copyright (c) 2023 Vendicated and Vencord contributors | ||||
|  */ | ||||
| 
 | ||||
| import "./hideGarbage.css"; | ||||
| import "./fixes.css"; | ||||
| 
 | ||||
| import { isWindows, localStorage } from "./utils"; | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										24
									
								
								src/renderer/patches/hideSwitchDevice.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								src/renderer/patches/hideSwitchDevice.tsx
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,24 @@ | |||
| /* | ||||
|  * SPDX-License-Identifier: GPL-3.0 | ||||
|  * Vesktop, a desktop app aiming to give you a snappier Discord Experience | ||||
|  * 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)\.default\.getState\(\).neverShowModal/, | ||||
|                 replace: "$& || $self.shouldIgnore($1)" | ||||
|             } | ||||
|         } | ||||
|     ], | ||||
| 
 | ||||
|     shouldIgnore(state: any) { | ||||
|         return Object.keys(state?.default?.lastDeviceConnected ?? {})?.[0] === "vencord-screen-share"; | ||||
|     } | ||||
| }); | ||||
							
								
								
									
										25
									
								
								src/renderer/patches/hideVenmicInput.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								src/renderer/patches/hideVenmicInput.tsx
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,25 @@ | |||
| /* | ||||
|  * SPDX-License-Identifier: GPL-3.0 | ||||
|  * Vesktop, a desktop app aiming to give you a snappier Discord Experience | ||||
|  * 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"); | ||||
|     } | ||||
| }); | ||||
|  | @ -7,6 +7,8 @@ | |||
| // TODO: Possibly auto generate glob if we have more patches in the future
 | ||||
| import "./enableNotificationsByDefault"; | ||||
| import "./platformClass"; | ||||
| import "./screenShareAudio"; | ||||
| import "./hideSwitchDevice"; | ||||
| import "./hideVenmicInput"; | ||||
| import "./screenShareFixes"; | ||||
| import "./spellCheck"; | ||||
| import "./windowsTitleBar"; | ||||
|  |  | |||
|  | @ -4,8 +4,12 @@ | |||
|  * 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; | ||||
| 
 | ||||
|  | @ -23,6 +27,29 @@ if (isLinux) { | |||
|         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, | ||||
|             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: { | ||||
							
								
								
									
										2
									
								
								src/shared/settings.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								src/shared/settings.d.ts
									
									
									
									
										vendored
									
									
								
							|  | @ -20,7 +20,7 @@ export interface Settings { | |||
|     arRPC?: boolean; | ||||
|     appBadge?: boolean; | ||||
|     disableMinSize?: boolean; | ||||
| 
 | ||||
|     clickTrayToShowHide?: boolean; | ||||
|     /** @deprecated use customTitleBar */ | ||||
|     discordWindowsTitleBar?: boolean; | ||||
|     customTitleBar?: boolean; | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue
	
	 Sqaaakoi
						Sqaaakoi