Compare commits
15 Commits
dependency
...
autogain
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cd2aca55c7 | ||
|
|
8a6f49b949 | ||
|
|
47fd620876 | ||
|
|
c15250498b | ||
|
|
6f0303206e | ||
|
|
6e86647c95 | ||
|
|
798fb3d5e4 | ||
|
|
6c9b76ed90 | ||
|
|
b582584c69 | ||
|
|
7b6e8fc473 | ||
|
|
8f0a810539 | ||
|
|
a6eb82948f | ||
|
|
24727f398a | ||
|
|
c43e9953a5 | ||
|
|
08cb713e8c |
@@ -45,6 +45,7 @@ set(discord-screenaudio_SRC
|
|||||||
src/log.cpp
|
src/log.cpp
|
||||||
src/userscript.cpp
|
src/userscript.cpp
|
||||||
src/centralwidget.cpp
|
src/centralwidget.cpp
|
||||||
|
src/localserver.cpp
|
||||||
resources.qrc
|
resources.qrc
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -62,6 +62,9 @@ You have multiple options:
|
|||||||
With apt:
|
With apt:
|
||||||
`apt install -y build-essential cmake qtbase5-dev qtwebengine5-dev libkf5notifications-dev libkf5xmlgui-dev libkf5globalaccel-dev pkg-config libpipewire-0.3-dev git`
|
`apt install -y build-essential cmake qtbase5-dev qtwebengine5-dev libkf5notifications-dev libkf5xmlgui-dev libkf5globalaccel-dev pkg-config libpipewire-0.3-dev git`
|
||||||
|
|
||||||
|
With dnf:
|
||||||
|
`dnf install @development-tools cmake qt5-qtbase-devel qt5-qtwebengine-devel kf5-knotifications-devel kf5-kxmlgui-devel kf5-kglobalaccel-devel pkgconfig pipewire-devel git`
|
||||||
|
|
||||||
### Building
|
### Building
|
||||||
|
|
||||||
First, clone the repository:
|
First, clone the repository:
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
navigator.mediaDevices.chromiumGetDisplayMedia =
|
navigator.mediaDevices.chromiumGetDisplayMedia =
|
||||||
navigator.mediaDevices.getDisplayMedia;
|
navigator.mediaDevices.getDisplayMedia;
|
||||||
|
navigator.mediaDevices.chromiumGetUserMedia =
|
||||||
|
navigator.mediaDevices.getUserMedia;
|
||||||
|
|
||||||
function sleep(ms) {
|
function sleep(ms) {
|
||||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
}
|
}
|
||||||
|
|
||||||
const getAudioDevice = async (nameOfAudioDevice) => {
|
const getAudioDevice = async (nameOfAudioDevice) => {
|
||||||
await navigator.mediaDevices.getUserMedia({
|
await navigator.mediaDevices.chromiumGetUserMedia({
|
||||||
audio: true,
|
audio: true,
|
||||||
});
|
});
|
||||||
let audioDevice;
|
let audioDevice;
|
||||||
@@ -23,6 +25,16 @@ const getAudioDevice = async (nameOfAudioDevice) => {
|
|||||||
return audioDevice;
|
return audioDevice;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function setGetUserMedia() {
|
||||||
|
const getUserMedia = async (constraints) => {
|
||||||
|
return await navigator.mediaDevices.chromiumGetUserMedia({
|
||||||
|
video: constraints?.video || false,
|
||||||
|
audio: { ...constraints?.audio, autoGainControl },
|
||||||
|
});
|
||||||
|
};
|
||||||
|
navigator.mediaDevices.getUserMedia = getUserMedia;
|
||||||
|
}
|
||||||
|
|
||||||
function setGetDisplayMedia(video = true, overrideArgs = undefined) {
|
function setGetDisplayMedia(video = true, overrideArgs = undefined) {
|
||||||
const getDisplayMedia = async (...args) => {
|
const getDisplayMedia = async (...args) => {
|
||||||
var id;
|
var id;
|
||||||
@@ -34,26 +46,27 @@ function setGetDisplayMedia(video = true, overrideArgs = undefined) {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
id = "default";
|
id = "default";
|
||||||
}
|
}
|
||||||
let captureSystemAudioStream = await navigator.mediaDevices.getUserMedia({
|
let captureSystemAudioStream =
|
||||||
audio: {
|
await navigator.mediaDevices.chromiumGetUserMedia({
|
||||||
// We add our audio constraints here, to get a list of supported constraints use navigator.mediaDevices.getSupportedConstraints();
|
audio: {
|
||||||
// We must capture a microphone, we use default since its the only deviceId that is the same for every Chromium user
|
// We add our audio constraints here, to get a list of supported constraints use navigator.mediaDevices.getSupportedConstraints();
|
||||||
deviceId: {
|
// We must capture a microphone, we use default since its the only deviceId that is the same for every Chromium user
|
||||||
exact: id,
|
deviceId: {
|
||||||
|
exact: id,
|
||||||
|
},
|
||||||
|
// We want auto gain control, noise cancellation and noise suppression disabled so that our stream won't sound bad
|
||||||
|
autoGainControl: false,
|
||||||
|
echoCancellation: false,
|
||||||
|
noiseSuppression: false,
|
||||||
|
// By default Chromium sets channel count for audio devices to 1, we want it to be stereo in case we find a way for Discord to accept stereo screenshare too
|
||||||
|
channelCount: 2,
|
||||||
|
// You can set more audio constraints here, bellow are some examples
|
||||||
|
//latency: 0,
|
||||||
|
//sampleRate: 48000,
|
||||||
|
//sampleSize: 16,
|
||||||
|
//volume: 1.0
|
||||||
},
|
},
|
||||||
// We want auto gain control, noise cancellation and noise suppression disabled so that our stream won't sound bad
|
});
|
||||||
autoGainControl: false,
|
|
||||||
echoCancellation: false,
|
|
||||||
noiseSuppression: false,
|
|
||||||
// By default Chromium sets channel count for audio devices to 1, we want it to be stereo in case we find a way for Discord to accept stereo screenshare too
|
|
||||||
channelCount: 2,
|
|
||||||
// You can set more audio constraints here, bellow are some examples
|
|
||||||
//latency: 0,
|
|
||||||
//sampleRate: 48000,
|
|
||||||
//sampleSize: 16,
|
|
||||||
//volume: 1.0
|
|
||||||
},
|
|
||||||
});
|
|
||||||
let [track] = captureSystemAudioStream.getAudioTracks();
|
let [track] = captureSystemAudioStream.getAudioTracks();
|
||||||
const gdm = await navigator.mediaDevices.chromiumGetDisplayMedia(
|
const gdm = await navigator.mediaDevices.chromiumGetDisplayMedia(
|
||||||
...(overrideArgs
|
...(overrideArgs
|
||||||
@@ -68,6 +81,7 @@ function setGetDisplayMedia(video = true, overrideArgs = undefined) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setGetDisplayMedia();
|
setGetDisplayMedia();
|
||||||
|
setGetUserMedia();
|
||||||
|
|
||||||
let userscript;
|
let userscript;
|
||||||
let muteBtn;
|
let muteBtn;
|
||||||
@@ -76,6 +90,7 @@ let streamStartBtn;
|
|||||||
let streamStartBtnInitialDisplay;
|
let streamStartBtnInitialDisplay;
|
||||||
let streamStartBtnClone;
|
let streamStartBtnClone;
|
||||||
let resolutionString;
|
let resolutionString;
|
||||||
|
let autoGainControl = true;
|
||||||
const clonedElements = [];
|
const clonedElements = [];
|
||||||
const hiddenElements = [];
|
const hiddenElements = [];
|
||||||
let wasStreamActive = false;
|
let wasStreamActive = false;
|
||||||
@@ -159,6 +174,10 @@ function main() {
|
|||||||
streamStartBtnClone.remove();
|
streamStartBtnClone.remove();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
userscript.getPref("disableAutomaticGain", false).then((disabled) => {
|
||||||
|
autoGainControl = !disabled;
|
||||||
|
});
|
||||||
|
|
||||||
function updateUserstyles() {
|
function updateUserstyles() {
|
||||||
userscript.log("Loading userstyles...");
|
userscript.log("Loading userstyles...");
|
||||||
userscript.loadingMessage = "Loading userstyles...";
|
userscript.loadingMessage = "Loading userstyles...";
|
||||||
@@ -368,5 +387,29 @@ function main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const el of document.getElementsByClassName("sensitivity-3A7Gs9")) {
|
||||||
|
if (
|
||||||
|
el.getElementsByTagName("div").length > 0 &&
|
||||||
|
!document.getElementById("discord-screenaudio-gaintoggle")
|
||||||
|
) {
|
||||||
|
const toggle = createSwitch(
|
||||||
|
"Disable automatic gain",
|
||||||
|
await userscript.getPref("disableAutomaticGain", false),
|
||||||
|
async (disabled) => {
|
||||||
|
await userscript.setPref("disableAutomaticGain", disabled);
|
||||||
|
autoGainControl = !disabled;
|
||||||
|
setGetUserMedia();
|
||||||
|
if (disabled)
|
||||||
|
userscript.showInformation(
|
||||||
|
"discord-screenaudio",
|
||||||
|
"If you are currently in a call, this setting may only take effect after you rejoin the call or restart discord-screenaudio."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
toggle.id = "discord-screenaudio-gaintoggle";
|
||||||
|
el.getElementsByTagName("div")[0].appendChild(toggle);
|
||||||
|
}
|
||||||
|
}
|
||||||
}, 500);
|
}, 500);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -282,10 +282,11 @@ void DiscordPage::javaScriptConsoleMessage(
|
|||||||
ansi += "\033[" + cssAnsiColorMap[color] + "m";
|
ansi += "\033[" + cssAnsiColorMap[color] + "m";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
qDebug(discordLog) << (ansi + lines[0].trimmed() + "\033[0m " +
|
if (endOfStyles < lines.length())
|
||||||
lines[endOfStyles].trimmed())
|
qDebug(discordLog) << (ansi + lines[0].trimmed() + "\033[0m " +
|
||||||
.toUtf8()
|
lines[endOfStyles].trimmed())
|
||||||
.constData();
|
.toUtf8()
|
||||||
|
.constData();
|
||||||
for (auto line : lines.mid(endOfStyles + 1)) {
|
for (auto line : lines.mid(endOfStyles + 1)) {
|
||||||
qDebug(discordLog) << line.toUtf8().constData();
|
qDebug(discordLog) << line.toUtf8().constData();
|
||||||
}
|
}
|
||||||
|
|||||||
22
src/localserver.cpp
Normal file
22
src/localserver.cpp
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
#include "localserver.h"
|
||||||
|
|
||||||
|
bool isProgramRunning(const QString &program_name) {
|
||||||
|
QLocalSocket socket;
|
||||||
|
socket.connectToServer(program_name);
|
||||||
|
if (socket.waitForConnected()) {
|
||||||
|
return true; // program is already running
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void showErrorMessage(const char *text) {
|
||||||
|
QMessageBox msgBox;
|
||||||
|
|
||||||
|
msgBox.setIcon(QMessageBox::Critical);
|
||||||
|
msgBox.setText(text);
|
||||||
|
msgBox.setStandardButtons(QMessageBox::Ok);
|
||||||
|
msgBox.setDefaultButton(QMessageBox::Ok);
|
||||||
|
msgBox.setWindowIcon(QIcon(":assets/de.shorsh.discord-screenaudio.png"));
|
||||||
|
|
||||||
|
msgBox.exec();
|
||||||
|
}
|
||||||
9
src/localserver.h
Normal file
9
src/localserver.h
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "mainwindow.h"
|
||||||
|
|
||||||
|
#include <QLocalServer>
|
||||||
|
#include <QLocalSocket>
|
||||||
|
#include <QMessageBox>
|
||||||
|
|
||||||
|
bool isProgramRunning(const QString &program_name);
|
||||||
|
void showErrorMessage(const char *text);
|
||||||
19
src/main.cpp
19
src/main.cpp
@@ -1,3 +1,4 @@
|
|||||||
|
#include "localserver.h"
|
||||||
#include "mainwindow.h"
|
#include "mainwindow.h"
|
||||||
#include "virtmic.h"
|
#include "virtmic.h"
|
||||||
|
|
||||||
@@ -7,10 +8,14 @@
|
|||||||
|
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
#include <QCommandLineParser>
|
#include <QCommandLineParser>
|
||||||
|
#include <QLocalServer>
|
||||||
|
#include <QLocalSocket>
|
||||||
#include <QLoggingCategory>
|
#include <QLoggingCategory>
|
||||||
|
#include <QMessageBox>
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char *argv[]) {
|
||||||
QApplication app(argc, argv);
|
QApplication app(argc, argv);
|
||||||
|
|
||||||
QApplication::setApplicationName("discord-screenaudio");
|
QApplication::setApplicationName("discord-screenaudio");
|
||||||
QApplication::setWindowIcon(
|
QApplication::setWindowIcon(
|
||||||
QIcon(":assets/de.shorsh.discord-screenaudio.png"));
|
QIcon(":assets/de.shorsh.discord-screenaudio.png"));
|
||||||
@@ -50,6 +55,20 @@ int main(int argc, char *argv[]) {
|
|||||||
qgetenv("QTWEBENGINE_CHROMIUM_FLAGS"));
|
qgetenv("QTWEBENGINE_CHROMIUM_FLAGS"));
|
||||||
|
|
||||||
MainWindow w(parser.isSet(notifySendOption));
|
MainWindow w(parser.isSet(notifySendOption));
|
||||||
|
|
||||||
|
// Check if discord is already running
|
||||||
|
QString program_name = "discord-screenaudio";
|
||||||
|
if (isProgramRunning(program_name)) {
|
||||||
|
// if running show error message
|
||||||
|
showErrorMessage("discord-screenaudio is already running");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// open server so we can check if discord is running
|
||||||
|
QLocalServer server;
|
||||||
|
server.listen(program_name);
|
||||||
|
QObject::connect(&server, &QLocalServer::newConnection, []() {});
|
||||||
|
|
||||||
w.show();
|
w.show();
|
||||||
|
|
||||||
return app.exec();
|
return app.exec();
|
||||||
|
|||||||
@@ -44,6 +44,10 @@ void UserScript::setupHelpMenu() {
|
|||||||
QString(), "https://github.com/Curve");
|
QString(), "https://github.com/Curve");
|
||||||
aboutData.addComponent("Rohrkabel", "A C++ RAII Pipewire-API Wrapper", "1.3",
|
aboutData.addComponent("Rohrkabel", "A C++ RAII Pipewire-API Wrapper", "1.3",
|
||||||
"https://github.com/Soundux/rohrkabel");
|
"https://github.com/Soundux/rohrkabel");
|
||||||
|
aboutData.addComponent("arRPC",
|
||||||
|
"An open implementation of Discord's local RPC "
|
||||||
|
"servers<br>Copyright (c) 2022 OpenAsar",
|
||||||
|
nullptr, "https://github.com/OpenAsar/arrpc");
|
||||||
m_helpMenu = new KHelpMenu(MainWindow::instance(), aboutData);
|
m_helpMenu = new KHelpMenu(MainWindow::instance(), aboutData);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
@@ -178,3 +182,7 @@ void UserScript::showThemeDialog() {
|
|||||||
void UserScript::installUserStyles(QString url) {
|
void UserScript::installUserStyles(QString url) {
|
||||||
emit shouldInstallUserStyles(url);
|
emit shouldInstallUserStyles(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void UserScript::showInformation(QString title, QString message) {
|
||||||
|
QMessageBox::information(MainWindow::instance(), title, message);
|
||||||
|
}
|
||||||
|
|||||||
@@ -72,6 +72,7 @@ public Q_SLOTS:
|
|||||||
void showShortcutsDialog();
|
void showShortcutsDialog();
|
||||||
void showHelpMenu();
|
void showHelpMenu();
|
||||||
void showStreamDialog();
|
void showStreamDialog();
|
||||||
|
void showInformation(QString title, QString message);
|
||||||
void stopVirtmic();
|
void stopVirtmic();
|
||||||
void startVirtmic(QString target);
|
void startVirtmic(QString target);
|
||||||
void showThemeDialog();
|
void showThemeDialog();
|
||||||
|
|||||||
@@ -8,6 +8,25 @@ namespace Virtmic {
|
|||||||
|
|
||||||
const QStringList EXCLUDE_TARGETS{"Chromium input", "discord-screenaudio"};
|
const QStringList EXCLUDE_TARGETS{"Chromium input", "discord-screenaudio"};
|
||||||
|
|
||||||
|
const std::string nullstr = "";
|
||||||
|
const std::string &getTarget(const pipewire::spa::dict &props) {
|
||||||
|
if (props.count("media.class") &&
|
||||||
|
props.at("media.class") == "Stream/Output/Audio") {
|
||||||
|
if (props.count("application.name") && props.at("application.name") != "")
|
||||||
|
return props.at("application.name");
|
||||||
|
else if (props.count("application.process.binary") &&
|
||||||
|
props.at("application.process.binary") != "")
|
||||||
|
return props.at("application.process.binary");
|
||||||
|
else
|
||||||
|
return props.at("node.name");
|
||||||
|
} else
|
||||||
|
return nullstr;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString qGetTarget(const pipewire::spa::dict &props) {
|
||||||
|
return QString::fromStdString(getTarget(props));
|
||||||
|
}
|
||||||
|
|
||||||
QVector<QString> getTargets() {
|
QVector<QString> getTargets() {
|
||||||
auto main_loop = pipewire::main_loop();
|
auto main_loop = pipewire::main_loop();
|
||||||
auto context = pipewire::context(main_loop);
|
auto context = pipewire::context(main_loop);
|
||||||
@@ -22,14 +41,7 @@ QVector<QString> getTargets() {
|
|||||||
if (global.type == pipewire::node::type) {
|
if (global.type == pipewire::node::type) {
|
||||||
auto node = reg.bind<pipewire::node>(global.id);
|
auto node = reg.bind<pipewire::node>(global.id);
|
||||||
auto info = node.info();
|
auto info = node.info();
|
||||||
QString name;
|
QString name = qGetTarget(info.props);
|
||||||
if (info.props.count("application.name") &&
|
|
||||||
info.props["application.name"] != "")
|
|
||||||
name = QString::fromStdString(info.props["application.name"]);
|
|
||||||
else
|
|
||||||
name = QString::fromStdString(
|
|
||||||
info.props["application.process.binary"]);
|
|
||||||
|
|
||||||
if (name != "" && !EXCLUDE_TARGETS.contains(name) &&
|
if (name != "" && !EXCLUDE_TARGETS.contains(name) &&
|
||||||
!targets.contains(name)) {
|
!targets.contains(name)) {
|
||||||
targets.append(name);
|
targets.append(name);
|
||||||
@@ -73,13 +85,7 @@ void start(QString _target) {
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
auto &parent = nodes.at(parent_id);
|
auto &parent = nodes.at(parent_id);
|
||||||
std::string name;
|
std::string name = getTarget(parent.props);
|
||||||
if (parent.props.count("application.name") &&
|
|
||||||
parent.props["application.name"] != "")
|
|
||||||
name = parent.props["application.name"];
|
|
||||||
else
|
|
||||||
name = parent.props["application.process.binary"];
|
|
||||||
|
|
||||||
if (name == target ||
|
if (name == target ||
|
||||||
(target == "[All Desktop Audio]" &&
|
(target == "[All Desktop Audio]" &&
|
||||||
!EXCLUDE_TARGETS.contains(QString::fromStdString(name)))) {
|
!EXCLUDE_TARGETS.contains(QString::fromStdString(name)))) {
|
||||||
@@ -99,7 +105,7 @@ void start(QString _target) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
std::string target = _target.toLatin1().toStdString();
|
std::string target = _target.toUtf8().toStdString();
|
||||||
|
|
||||||
auto virtual_mic = core.create("adapter",
|
auto virtual_mic = core.create("adapter",
|
||||||
{{"node.name", "discord-screenaudio-virtmic"},
|
{{"node.name", "discord-screenaudio-virtmic"},
|
||||||
@@ -123,13 +129,8 @@ void start(QString _target) {
|
|||||||
if (global.type == pipewire::node::type) {
|
if (global.type == pipewire::node::type) {
|
||||||
auto node = reg.bind<pipewire::node>(global.id);
|
auto node = reg.bind<pipewire::node>(global.id);
|
||||||
auto info = node.info();
|
auto info = node.info();
|
||||||
std::string name;
|
std::string name = getTarget(info.props);
|
||||||
if (info.props.count("application.name") &&
|
if (name == nullstr)
|
||||||
info.props["application.name"] != "")
|
|
||||||
name = info.props["application.name"];
|
|
||||||
else if (info.props.count("application.process.binary")) {
|
|
||||||
name = info.props["application.process.binary"];
|
|
||||||
} else
|
|
||||||
return;
|
return;
|
||||||
qDebug(virtmicLog) << QString("Added: %1")
|
qDebug(virtmicLog) << QString("Added: %1")
|
||||||
.arg(QString::fromStdString(name))
|
.arg(QString::fromStdString(name))
|
||||||
@@ -168,12 +169,9 @@ void start(QString _target) {
|
|||||||
[&](const std::uint32_t id) {
|
[&](const std::uint32_t id) {
|
||||||
if (nodes.count(id)) {
|
if (nodes.count(id)) {
|
||||||
auto info = nodes.at(id);
|
auto info = nodes.at(id);
|
||||||
std::string name;
|
std::string name = getTarget(info.props);
|
||||||
if (info.props.count("application.name") &&
|
if (name == nullstr)
|
||||||
info.props["application.name"] != "")
|
return;
|
||||||
name = info.props["application.name"];
|
|
||||||
else
|
|
||||||
name = info.props["application.process.binary"];
|
|
||||||
qDebug(virtmicLog) << QString("Removed: %1")
|
qDebug(virtmicLog) << QString("Removed: %1")
|
||||||
.arg(QString::fromStdString(name))
|
.arg(QString::fromStdString(name))
|
||||||
.toUtf8()
|
.toUtf8()
|
||||||
|
|||||||
Reference in New Issue
Block a user