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",
|
||||||
|
|
|
@ -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…
Reference in a new issue