mirror of
				https://github.com/smartfrigde/armcord.git
				synced 2024-08-14 23:56:58 +00:00 
			
		
		
		
	feat: linux pulseaudio screenshare
This commit is contained in:
		
							parent
							
								
									3fc1757324
								
							
						
					
					
						commit
						e1e472bde3
					
				
					 12 changed files with 17 additions and 104 deletions
				
			
		|  | @ -36,7 +36,7 @@ | ||||||
|         "@typescript-eslint/eslint-plugin": "^5.59.2", |         "@typescript-eslint/eslint-plugin": "^5.59.2", | ||||||
|         "@typescript-eslint/parser": "^5.59.2", |         "@typescript-eslint/parser": "^5.59.2", | ||||||
|         "copyfiles": "^2.4.1", |         "copyfiles": "^2.4.1", | ||||||
|         "electron": "29.1.4", |         "electron": "30.0.1", | ||||||
|         "electron-builder": "^24.9.1", |         "electron-builder": "^24.9.1", | ||||||
|         "eslint": "^8.40.0", |         "eslint": "^8.40.0", | ||||||
|         "eslint-config-dmitmel": "github:dmitmel/eslint-config-dmitmel", |         "eslint-config-dmitmel": "github:dmitmel/eslint-config-dmitmel", | ||||||
|  |  | ||||||
							
								
								
									
										8
									
								
								pnpm-lock.yaml
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										8
									
								
								pnpm-lock.yaml
									
										
									
										generated
									
									
									
								
							|  | @ -41,8 +41,8 @@ devDependencies: | ||||||
|     specifier: ^2.4.1 |     specifier: ^2.4.1 | ||||||
|     version: 2.4.1 |     version: 2.4.1 | ||||||
|   electron: |   electron: | ||||||
|     specifier: 29.1.4 |     specifier: 30.0.1 | ||||||
|     version: 29.1.4 |     version: 30.0.1 | ||||||
|   electron-builder: |   electron-builder: | ||||||
|     specifier: ^24.9.1 |     specifier: ^24.9.1 | ||||||
|     version: 24.9.1 |     version: 24.9.1 | ||||||
|  | @ -1034,8 +1034,8 @@ packages: | ||||||
|       - supports-color |       - supports-color | ||||||
|     dev: true |     dev: true | ||||||
| 
 | 
 | ||||||
|   /electron@29.1.4: |   /electron@30.0.1: | ||||||
|     resolution: {integrity: sha512-IWXys0SqgmIfrqXusUGQC0gGG7CCqA5vfmNsUMj8dFkAnK3lisKyjSESStWlrsste/OX/AAC5wsVlf23reUNnw==} |     resolution: {integrity: sha512-iwxkI/n2wBd29NH7TH0ZY8aWGzCoKpzJz+D10u7aGSJi1TV6d4MSM3rWyKvT/UkAHkTKOEgYfUyCa2vWQm8L0g==} | ||||||
|     engines: {node: '>= 12.20.55'} |     engines: {node: '>= 12.20.55'} | ||||||
|     hasBin: true |     hasBin: true | ||||||
|     requiresBuild: true |     requiresBuild: true | ||||||
|  |  | ||||||
|  | @ -9,7 +9,6 @@ import { | ||||||
|     getLang, |     getLang, | ||||||
|     getLangName, |     getLangName, | ||||||
|     getVersion, |     getVersion, | ||||||
|     getWindowState, |  | ||||||
|     modInstallState, |     modInstallState, | ||||||
|     packageVersion, |     packageVersion, | ||||||
|     setConfigBulk, |     setConfigBulk, | ||||||
|  |  | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| import {BrowserWindow, app, globalShortcut, ipcMain, shell} from "electron"; | import {BrowserWindow, globalShortcut, ipcMain, shell} from "electron"; | ||||||
| import path from "path"; | import path from "path"; | ||||||
| import {getConfig, registerGlobalKeybinds, setConfig} from "../utils"; | import {getConfig, registerGlobalKeybinds, setConfig} from "../utils"; | ||||||
| let keybindWindow: BrowserWindow; | let keybindWindow: BrowserWindow; | ||||||
|  |  | ||||||
							
								
								
									
										10
									
								
								src/main.ts
									
										
									
									
									
								
							
							
						
						
									
										10
									
								
								src/main.ts
									
										
									
									
									
								
							|  | @ -11,15 +11,12 @@ import { | ||||||
|     getConfigSync, |     getConfigSync, | ||||||
|     injectElectronFlags, |     injectElectronFlags, | ||||||
|     installModLoader, |     installModLoader, | ||||||
|     modInstallState, |  | ||||||
|     setConfig, |     setConfig, | ||||||
|     setLang, |     setLang | ||||||
|     setWindowState, |  | ||||||
|     sleep |  | ||||||
| } from "./utils"; | } from "./utils"; | ||||||
| import "./extensions/mods"; | import "./extensions/mods"; | ||||||
| import "./tray"; | import "./tray"; | ||||||
| import {createCustomWindow, createNativeWindow, createTransparentWindow, mainWindow} from "./window"; | import {createCustomWindow, createNativeWindow, createTransparentWindow} from "./window"; | ||||||
| import path from "path"; | import path from "path"; | ||||||
| import {createTManagerWindow} from "./themeManager/main"; | import {createTManagerWindow} from "./themeManager/main"; | ||||||
| import {createSplashWindow} from "./splash/main"; | import {createSplashWindow} from "./splash/main"; | ||||||
|  | @ -67,7 +64,8 @@ if (!app.requestSingleInstanceLock() && getConfigSync("multiInstance") == (false | ||||||
|     crashReporter.start({uploadToServer: false}); |     crashReporter.start({uploadToServer: false}); | ||||||
|     // enable webrtc capturer for wayland
 |     // enable webrtc capturer for wayland
 | ||||||
|     if (process.platform === "linux" && process.env.XDG_SESSION_TYPE?.toLowerCase() === "wayland") { |     if (process.platform === "linux" && process.env.XDG_SESSION_TYPE?.toLowerCase() === "wayland") { | ||||||
|         app.commandLine.appendSwitch("enable-features=WebRTCPipeWireCapturer"); |         app.commandLine.appendSwitch("enable-features", "WebRTCPipeWireCapturer,PulseaudioLoopbackForScreenShare"); | ||||||
|  |         app.commandLine.appendSwitch("disable-features", "WebRtcAllowInputVolumeAdjustment"); | ||||||
|         console.log("Wayland detected, using PipeWire for video capture."); |         console.log("Wayland detected, using PipeWire for video capture."); | ||||||
|     } |     } | ||||||
|     // work around chrome 66 disabling autoplay by default
 |     // work around chrome 66 disabling autoplay by default
 | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| import * as path from "path"; | import * as path from "path"; | ||||||
| import * as fs from "fs"; | import * as fs from "fs"; | ||||||
| import {addScript, addStyle} from "../utils"; | import {addStyle} from "../utils"; | ||||||
| import {WebviewTag} from "electron"; | import {WebviewTag} from "electron"; | ||||||
| 
 | 
 | ||||||
| var webview = `<webview src="${path.join("file://", __dirname, "../", "/settings/settings.html")}" preload="${path.join( | var webview = `<webview src="${path.join("file://", __dirname, "../", "/settings/settings.html")}" preload="${path.join( | ||||||
|  |  | ||||||
|  | @ -1,79 +0,0 @@ | ||||||
| import {spawn} from "child_process"; |  | ||||||
| import fs from "fs"; |  | ||||||
| import {app} from "electron"; |  | ||||||
| import path from "path"; |  | ||||||
| 
 |  | ||||||
| // quick and dirty way for audio screenshare on Linux
 |  | ||||||
| // please PR a better non-shell solution that isn't a dependency mess
 |  | ||||||
| //src https://github.com/edisionnano/Screenshare-with-audio-on-Discord-with-Linux
 |  | ||||||
| interface SinkInput { |  | ||||||
|     id: string; |  | ||||||
|     properties: Record<string, string>; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| const parseSinkInputs = (input: string): SinkInput[] => { |  | ||||||
|     const regex = /Sink Input #(\d+)\n([\s\S]+?)(?=\nSink Input #|\n{2,}|$)/g; |  | ||||||
|     const result: SinkInput[] = []; |  | ||||||
|     let match; |  | ||||||
| 
 |  | ||||||
|     while ((match = regex.exec(input))) { |  | ||||||
|         const sinkInput: SinkInput = { |  | ||||||
|             id: match[1], |  | ||||||
|             properties: {} |  | ||||||
|         }; |  | ||||||
| 
 |  | ||||||
|         const propertiesRegex = /(\w+)\s*=\s*"([^"]*)"/g; |  | ||||||
|         let propertiesMatch; |  | ||||||
| 
 |  | ||||||
|         while ((propertiesMatch = propertiesRegex.exec(match[2]))) { |  | ||||||
|             sinkInput.properties[propertiesMatch[1]] = propertiesMatch[2]; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         result.push(sinkInput); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return result; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export function createVirtualDevice(sinkInput: number) { |  | ||||||
|     var script = ` |  | ||||||
|     SINK_NAME=armcord |  | ||||||
|     export LC_ALL=C |  | ||||||
|     DEFAULT_OUTPUT=$(pactl info|sed -n -e 's/^.*Default Sink: //p') |  | ||||||
|     pactl load-module module-null-sink sink_name=$SINK_NAME |  | ||||||
|     pactl move-sink-input ${sinkInput} $SINK_NAME |  | ||||||
|     pactl load-module module-loopback source=$SINK_NAME.monitor sink=$DEFAULT_OUTPUT |  | ||||||
|     if pactl info|grep -w "PipeWire">/dev/null; then |  | ||||||
|         nohup pw-loopback --capture-props='node.target='$SINK_NAME --playback-props='media.class=Audio/Source node.name=virtmic node.description="virtmic"' >/dev/null & |  | ||||||
|     else |  | ||||||
|         pactl load-module module-remap-source master=$SINK_NAME.monitor source_name=virtmic source_properties=device.description=virtmic |  | ||||||
|     fi`;
 |  | ||||||
|     let scriptPath = path.join(app.getPath("temp"), "/", "script.sh"); |  | ||||||
|     spawn("chmod +x " + scriptPath); |  | ||||||
|     fs.writeFileSync(scriptPath, script, "utf-8"); |  | ||||||
|     const exec = spawn(scriptPath); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| export function isAudioSupported(): boolean { |  | ||||||
|     const pactl = spawn("pactl"); |  | ||||||
| 
 |  | ||||||
|     pactl.on("close", (code) => { |  | ||||||
|         if (code == 0) { |  | ||||||
|             return true; |  | ||||||
|         } |  | ||||||
|     }); |  | ||||||
|     return false; |  | ||||||
| } |  | ||||||
| export function getSinks() { |  | ||||||
|     const pactl = spawn("pactl list sink-inputs"); |  | ||||||
| 
 |  | ||||||
|     pactl.stderr.on("data", (data) => { |  | ||||||
|         console.log(data); |  | ||||||
|         return parseSinkInputs(data); |  | ||||||
|     }); |  | ||||||
|     pactl.on("close", (code) => { |  | ||||||
|         if (code !== 0) { |  | ||||||
|             throw Error("Couldn't get list of available apps for audio stream"); |  | ||||||
|         } |  | ||||||
|     }); |  | ||||||
| } |  | ||||||
|  | @ -41,8 +41,8 @@ function registerCustomHandler(): void { | ||||||
|                 //console.log(id);
 |                 //console.log(id);
 | ||||||
|                 capturerWindow.close(); |                 capturerWindow.close(); | ||||||
|                 let result = {id, name, width: 9999, height: 9999}; |                 let result = {id, name, width: 9999, height: 9999}; | ||||||
|                 if (process.platform === "win32") { |                 if (process.platform === "linux") { | ||||||
|                     callback({video: result, audio: "loopback"}); |                     callback({video: result, audio: "loopbackWithMute"}); | ||||||
|                 } else { |                 } else { | ||||||
|                     callback({video: result}); |                     callback({video: result}); | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| import {BrowserWindow, app, ipcMain, shell} from "electron"; | import {BrowserWindow, app, shell} from "electron"; | ||||||
| import {getDisplayVersion} from "../utils"; | import {getDisplayVersion} from "../utils"; | ||||||
| import path from "path"; | import path from "path"; | ||||||
| import fs from "fs"; | import fs from "fs"; | ||||||
|  |  | ||||||
|  | @ -1,7 +1,7 @@ | ||||||
| import * as fs from "fs"; | import * as fs from "fs"; | ||||||
| import {Menu, MessageBoxOptions, Tray, app, dialog, nativeImage} from "electron"; | import {Menu, MessageBoxOptions, Tray, app, dialog, nativeImage} from "electron"; | ||||||
| import {createInviteWindow, mainWindow} from "./window"; | import {createInviteWindow, mainWindow} from "./window"; | ||||||
| import {getConfig, getConfigLocation, getDisplayVersion, setConfig, setWindowState} from "./utils"; | import {getConfig, getConfigLocation, getDisplayVersion, setConfig} from "./utils"; | ||||||
| import * as path from "path"; | import * as path from "path"; | ||||||
| import {createSettingsWindow} from "./settings/main"; | import {createSettingsWindow} from "./settings/main"; | ||||||
| export let tray: any = null; | export let tray: any = null; | ||||||
|  |  | ||||||
|  | @ -9,13 +9,9 @@ import { | ||||||
|     firstRun, |     firstRun, | ||||||
|     getConfig, |     getConfig, | ||||||
|     getWindowState, |     getWindowState, | ||||||
|     modInstallState, |  | ||||||
|     registerGlobalKeybinds, |     registerGlobalKeybinds, | ||||||
|     setConfig, |     setConfig, | ||||||
|     setLang, |     setWindowState | ||||||
|     setWindowState, |  | ||||||
|     sleep, |  | ||||||
|     transparency |  | ||||||
| } from "./utils"; | } from "./utils"; | ||||||
| import {registerIpc} from "./ipc"; | import {registerIpc} from "./ipc"; | ||||||
| import {setMenu} from "./menu"; | import {setMenu} from "./menu"; | ||||||
|  | @ -24,7 +20,6 @@ import contextMenu from "electron-context-menu"; | ||||||
| import os from "os"; | import os from "os"; | ||||||
| import {tray} from "./tray"; | import {tray} from "./tray"; | ||||||
| import {iconPath} from "./main"; | import {iconPath} from "./main"; | ||||||
| import {createSetupWindow} from "./setup/main"; |  | ||||||
| export let mainWindow: BrowserWindow; | export let mainWindow: BrowserWindow; | ||||||
| export let inviteWindow: BrowserWindow; | export let inviteWindow: BrowserWindow; | ||||||
| let forceQuit = false; | let forceQuit = false; | ||||||
|  |  | ||||||
|  | @ -16,7 +16,7 @@ | ||||||
|         "esModuleInterop": true, // Enables compatibility with Node.js' module system since the entire export can be whatever you want. allowSyntheticDefaultImports doesn't address runtime issues and is made redundant by this setting. |         "esModuleInterop": true, // Enables compatibility with Node.js' module system since the entire export can be whatever you want. allowSyntheticDefaultImports doesn't address runtime issues and is made redundant by this setting. | ||||||
|         "resolveJsonModule": true, // Allows you to import JSON files just like how you can require() them. Do note that if you're accessing any JSON files outside of src, it'll mess up dist. |         "resolveJsonModule": true, // Allows you to import JSON files just like how you can require() them. Do note that if you're accessing any JSON files outside of src, it'll mess up dist. | ||||||
|         "lib": ["ES2020", "DOM"], // Specifies what common libraries you have access to. If you're working in Node.js, you'll want to leave out the DOM library. But do make sure to include "@types/node" because otherwise, variables like "console" won't be defined. |         "lib": ["ES2020", "DOM"], // Specifies what common libraries you have access to. If you're working in Node.js, you'll want to leave out the DOM library. But do make sure to include "@types/node" because otherwise, variables like "console" won't be defined. | ||||||
| 
 |         "noUnusedLocals": true, // Warns you if you have unused variables. | ||||||
|         // Output // |         // Output // | ||||||
|         "module": "CommonJS", // Compiles ES6 imports to require() syntax. |         "module": "CommonJS", // Compiles ES6 imports to require() syntax. | ||||||
|         "removeComments": false, |         "removeComments": false, | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue