feat: linux pulseaudio screenshare

This commit is contained in:
smartfrigde 2024-04-28 12:56:51 +02:00
parent 3fc1757324
commit e1e472bde3
12 changed files with 17 additions and 104 deletions

View file

@ -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",

View file

@ -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

View file

@ -9,7 +9,6 @@ import {
getLang, getLang,
getLangName, getLangName,
getVersion, getVersion,
getWindowState,
modInstallState, modInstallState,
packageVersion, packageVersion,
setConfigBulk, setConfigBulk,

View file

@ -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;

View file

@ -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

View file

@ -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(

View file

@ -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");
}
});
}

View file

@ -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});
} }

View file

@ -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";

View file

@ -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;

View file

@ -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;

View file

@ -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,