Compare commits
18 Commits
virtmic-re
...
58c3ac0b71
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
58c3ac0b71 | ||
|
|
2858eb3854 | ||
|
|
94b27f5b6a | ||
|
|
8a6f49b949 | ||
|
|
47fd620876 | ||
|
|
c15250498b | ||
|
|
6f0303206e | ||
|
|
6e86647c95 | ||
|
|
798fb3d5e4 | ||
|
|
6c9b76ed90 | ||
|
|
b582584c69 | ||
|
|
7b6e8fc473 | ||
|
|
8f0a810539 | ||
|
|
a6eb82948f | ||
|
|
24727f398a | ||
|
|
c43e9953a5 | ||
|
|
08cb713e8c | ||
|
|
27faed4a3a |
23
.gitignore
vendored
23
.gitignore
vendored
@@ -1,3 +1,20 @@
|
|||||||
/build
|
# Ignore build and output directories
|
||||||
.vscode
|
/build/
|
||||||
/submodules/arrpc
|
/.flatpak-builder/
|
||||||
|
|
||||||
|
# Ignore flatpak-builder cache
|
||||||
|
/.flatpak-builder-cache/
|
||||||
|
|
||||||
|
# Ignore flatpak-builder log files
|
||||||
|
/*.log
|
||||||
|
|
||||||
|
# Ignore flatpak-builder generated files
|
||||||
|
/*.flatpak
|
||||||
|
/*.flatpak-builder
|
||||||
|
/*.flatpak-origin
|
||||||
|
|
||||||
|
# Ignore specific files or directories
|
||||||
|
/override.json
|
||||||
|
/metadata/
|
||||||
|
/manifest.json
|
||||||
|
/CMakeLists.txt.user
|
||||||
|
|||||||
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -1,6 +1,3 @@
|
|||||||
[submodule "submodules/rohrkabel"]
|
[submodule "submodules/rohrkabel"]
|
||||||
path = submodules/rohrkabel
|
path = submodules/rohrkabel
|
||||||
url = https://github.com/Soundux/rohrkabel
|
url = https://github.com/Soundux/rohrkabel
|
||||||
[submodule "submodules/channel"]
|
|
||||||
path = submodules/channel
|
|
||||||
url = https://github.com/Soundux/channel.git
|
|
||||||
|
|||||||
@@ -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
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -63,20 +64,15 @@ if(GIT_FOUND AND EXISTS "${PROJECT_SOURCE_DIR}/.git")
|
|||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
function(add_git_subdirectory SUBMODULE)
|
if(NOT EXISTS "${PROJECT_SOURCE_DIR}/submodules/rohrkabel/CMakeLists.txt")
|
||||||
if(NOT EXISTS "${PROJECT_SOURCE_DIR}/submodules/${SUBMODULE}/CMakeLists.txt")
|
message(FATAL_ERROR "Rohrkabel was not found since you are not in a Git checkout or have GIT_SUBMODULE disabled. Please provide rohrkabel manually to `./submodules/rohrkabel`.")
|
||||||
message(FATAL_ERROR "Submodule ${SUBMODULE} was not found since you are not in a Git checkout or have GIT_SUBMODULE disabled. Please provide ${SUBMODULE} manually to `./submodules/${SUBMODULE}`.")
|
endif()
|
||||||
endif()
|
|
||||||
|
|
||||||
add_subdirectory(submodules/${SUBMODULE})
|
add_subdirectory(submodules/rohrkabel)
|
||||||
endfunction()
|
|
||||||
|
|
||||||
add_git_subdirectory(rohrkabel)
|
|
||||||
add_git_subdirectory(channel)
|
|
||||||
|
|
||||||
add_executable(discord-screenaudio ${discord-screenaudio_SRC})
|
add_executable(discord-screenaudio ${discord-screenaudio_SRC})
|
||||||
|
|
||||||
target_link_libraries(discord-screenaudio Qt::Widgets Qt::WebEngineWidgets rohrkabel channel)
|
target_link_libraries(discord-screenaudio Qt::Widgets Qt::WebEngineWidgets rohrkabel)
|
||||||
|
|
||||||
if(KF5Notifications_FOUND)
|
if(KF5Notifications_FOUND)
|
||||||
target_link_libraries(discord-screenaudio KF5::Notifications)
|
target_link_libraries(discord-screenaudio KF5::Notifications)
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
BIN
de.shorsh.discord-screenaudio.png
Normal file
BIN
de.shorsh.discord-screenaudio.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 48 KiB |
28
install.sh
Executable file
28
install.sh
Executable file
@@ -0,0 +1,28 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Build Flatpak package
|
||||||
|
flatpak-builder build-dir lol.deadzone.discord-awesomeaudio.json --install --user --force-clean
|
||||||
|
|
||||||
|
# Create wrapper script
|
||||||
|
echo '#!/bin/bash
|
||||||
|
RUNNING=$(pgrep -f "discord-screenaudio")
|
||||||
|
if [[ -n $RUNNING ]]; then
|
||||||
|
WM_CLASS="discord-screenaudio"
|
||||||
|
else
|
||||||
|
WM_CLASS=""
|
||||||
|
fi
|
||||||
|
flatpak run --env=WM_CLASS="$WM_CLASS" lol.deadzone.discord-awesomeaudio' > ~/.local/bin/discord-awesomeaudio.sh
|
||||||
|
|
||||||
|
# Make the wrapper script executable
|
||||||
|
chmod +x ~/.local/bin/discord-awesomeaudio.sh
|
||||||
|
|
||||||
|
# Create .desktop file
|
||||||
|
echo "[Desktop Entry]
|
||||||
|
Name=Discord Awesome Audio
|
||||||
|
Exec=flatpak run lol.deadzone.discord-awesomeaudio
|
||||||
|
Icon=/path/to/application/icon.png
|
||||||
|
Type=Application
|
||||||
|
Categories=AudioVideo;Network;" > ~/.local/share/applications/discord-awesomeaudio.desktop
|
||||||
|
|
||||||
|
# Make the .desktop file executable (if necessary)
|
||||||
|
chmod +x ~/.local/share/applications/discord-awesomeaudio.desktop
|
||||||
44
lol.deadzone.discord-awesomeaudio.json
Normal file
44
lol.deadzone.discord-awesomeaudio.json
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
{
|
||||||
|
"app-id": "lol.deadzone.discord-awesomeaudio",
|
||||||
|
"runtime": "org.kde.Platform",
|
||||||
|
"runtime-version": "5.15-22.08",
|
||||||
|
"sdk": "org.kde.Sdk",
|
||||||
|
"base": "io.qt.qtwebengine.BaseApp",
|
||||||
|
"base-version": "5.15-22.08",
|
||||||
|
"command": "discord-screenaudio",
|
||||||
|
"finish-args": [
|
||||||
|
"--share=ipc",
|
||||||
|
"--share=network",
|
||||||
|
"--socket=wayland",
|
||||||
|
"--socket=fallback-x11",
|
||||||
|
"--socket=pulseaudio",
|
||||||
|
"--filesystem=xdg-videos:ro",
|
||||||
|
"--filesystem=xdg-pictures:ro",
|
||||||
|
"--filesystem=xdg-download",
|
||||||
|
"--filesystem=xdg-run/pipewire-0",
|
||||||
|
"--device=all",
|
||||||
|
"--talk-name=org.kde.StatusNotifierWatcher",
|
||||||
|
"--env=QTWEBENGINEPROCESS_PATH=/app/bin/QtWebEngineProcess",
|
||||||
|
"--talk-name=org.freedesktop.Notifications",
|
||||||
|
"--talk-name=org.kde.kglobalaccel"
|
||||||
|
],
|
||||||
|
"modules": [
|
||||||
|
{
|
||||||
|
"name": "discord-screenaudio",
|
||||||
|
"buildsystem": "cmake-ninja",
|
||||||
|
"config-opts": [
|
||||||
|
"-DCMAKE_BUILD_TYPE=RelWithDebInfo",
|
||||||
|
"-DGIT_SUBMODULE=OFF"
|
||||||
|
],
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"type": "dir",
|
||||||
|
"path": "."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"finish-install": {
|
||||||
|
"desktop-file": "[Desktop Entry]\nName=Discord Awesome Audio\nExec=flatpak run lol.deadzone.discord-awesomeaudio\nIcon=/path/to/application/icon.png\nType=Application\nCategories=AudioVideo;Network;"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
26
src/main.cpp
26
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"));
|
||||||
@@ -24,6 +29,9 @@ int main(int argc, char *argv[]) {
|
|||||||
"Custom Discord client with the ability to stream audio on Linux");
|
"Custom Discord client with the ability to stream audio on Linux");
|
||||||
parser.addHelpOption();
|
parser.addHelpOption();
|
||||||
parser.addVersionOption();
|
parser.addVersionOption();
|
||||||
|
QCommandLineOption virtmicOption("virtmic", "Start the Virtual Microphone",
|
||||||
|
"target");
|
||||||
|
parser.addOption(virtmicOption);
|
||||||
QCommandLineOption degubOption("remote-debugging",
|
QCommandLineOption degubOption("remote-debugging",
|
||||||
"Open Chromium Remote Debugging on port 9222");
|
"Open Chromium Remote Debugging on port 9222");
|
||||||
parser.addOption(degubOption);
|
parser.addOption(degubOption);
|
||||||
@@ -33,6 +41,10 @@ int main(int argc, char *argv[]) {
|
|||||||
|
|
||||||
parser.process(app);
|
parser.process(app);
|
||||||
|
|
||||||
|
if (parser.isSet(virtmicOption)) {
|
||||||
|
Virtmic::start(parser.value(virtmicOption));
|
||||||
|
}
|
||||||
|
|
||||||
qputenv("QTWEBENGINE_CHROMIUM_FLAGS",
|
qputenv("QTWEBENGINE_CHROMIUM_FLAGS",
|
||||||
"--enable-features=WebRTCPipeWireCapturer " +
|
"--enable-features=WebRTCPipeWireCapturer " +
|
||||||
qgetenv("QTWEBENGINE_CHROMIUM_FLAGS"));
|
qgetenv("QTWEBENGINE_CHROMIUM_FLAGS"));
|
||||||
@@ -43,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();
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
#include <QGridLayout>
|
#include <QGridLayout>
|
||||||
#include <QLabel>
|
#include <QLabel>
|
||||||
#include <QPushButton>
|
#include <QPushButton>
|
||||||
|
#include <QShortcut>
|
||||||
#include <QSpacerItem>
|
#include <QSpacerItem>
|
||||||
#include <QThread>
|
#include <QThread>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
@@ -30,6 +31,9 @@ MainWindow::MainWindow(bool useNotifySend, QWidget *parent)
|
|||||||
m_centralWidget = new CentralWidget(this);
|
m_centralWidget = new CentralWidget(this);
|
||||||
setCentralWidget(m_centralWidget);
|
setCentralWidget(m_centralWidget);
|
||||||
setupTrayIcon();
|
setupTrayIcon();
|
||||||
|
setMinimumSize(800, 300);
|
||||||
|
connect(new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_Q), this),
|
||||||
|
&QShortcut::activated, this, &MainWindow::toggleOrCloseWindow);
|
||||||
if (m_settings->contains("geometry")) {
|
if (m_settings->contains("geometry")) {
|
||||||
restoreGeometry(m_settings->value("geometry").toByteArray());
|
restoreGeometry(m_settings->value("geometry").toByteArray());
|
||||||
} else {
|
} else {
|
||||||
@@ -78,12 +82,7 @@ void MainWindow::setupTrayIcon() {
|
|||||||
|
|
||||||
connect(m_trayIcon, &QSystemTrayIcon::activated, [this](auto reason) {
|
connect(m_trayIcon, &QSystemTrayIcon::activated, [this](auto reason) {
|
||||||
if (reason == QSystemTrayIcon::Trigger) {
|
if (reason == QSystemTrayIcon::Trigger) {
|
||||||
if (isVisible()) {
|
toggleOrCloseWindow();
|
||||||
hide();
|
|
||||||
} else {
|
|
||||||
show();
|
|
||||||
activateWindow();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -130,3 +129,15 @@ MainWindow *MainWindow::instance() { return m_instance; }
|
|||||||
CentralWidget *MainWindow::centralWidget() {
|
CentralWidget *MainWindow::centralWidget() {
|
||||||
return instance()->m_centralWidget;
|
return instance()->m_centralWidget;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
void MainWindow::toggleOrCloseWindow() {
|
||||||
|
if (isVisible()) {
|
||||||
|
if (m_trayIcon == nullptr)
|
||||||
|
QApplication::quit();
|
||||||
|
else
|
||||||
|
hide();
|
||||||
|
} else {
|
||||||
|
show();
|
||||||
|
activateWindow();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "centralwidget.h"
|
#include "centralwidget.h"
|
||||||
#include "virtmic.h"
|
|
||||||
|
|
||||||
#include <QMainWindow>
|
#include <QMainWindow>
|
||||||
#include <QMenu>
|
#include <QMenu>
|
||||||
@@ -37,4 +36,5 @@ private:
|
|||||||
public Q_SLOTS:
|
public Q_SLOTS:
|
||||||
void setTrayIcon(bool enabled);
|
void setTrayIcon(bool enabled);
|
||||||
void fullScreenRequested(QWebEngineFullScreenRequest fullScreenRequest);
|
void fullScreenRequested(QWebEngineFullScreenRequest fullScreenRequest);
|
||||||
|
void toggleOrCloseWindow();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
#include "streamdialog.h"
|
#include "streamdialog.h"
|
||||||
#include "mainwindow.h"
|
|
||||||
#include "virtmic.h"
|
#include "virtmic.h"
|
||||||
|
|
||||||
#include <QComboBox>
|
#include <QComboBox>
|
||||||
|
|||||||
@@ -42,12 +42,12 @@ void UserScript::setupHelpMenu() {
|
|||||||
aboutData.addCredit(
|
aboutData.addCredit(
|
||||||
"Curve", "For creating the Rohrkabel library used in this project.",
|
"Curve", "For creating the Rohrkabel library used in this project.",
|
||||||
QString(), "https://github.com/Curve");
|
QString(), "https://github.com/Curve");
|
||||||
aboutData.addComponent("Rohrkabel", "A C++ RAII Pipewire-API Wrapper", "1.5",
|
aboutData.addComponent("Rohrkabel", "A C++ RAII Pipewire-API Wrapper", "1.3",
|
||||||
"https://github.com/Soundux/rohrkabel");
|
"https://github.com/Soundux/rohrkabel");
|
||||||
aboutData.addComponent(
|
aboutData.addComponent("arRPC",
|
||||||
"Soundux/channel ",
|
"An open implementation of Discord's local RPC "
|
||||||
"A C++ implementation of Rust's std::sync::mpsc::channel", nullptr,
|
"servers<br>Copyright (c) 2022 OpenAsar",
|
||||||
"https://github.com/Soundux/channel");
|
nullptr, "https://github.com/OpenAsar/arrpc");
|
||||||
m_helpMenu = new KHelpMenu(MainWindow::instance(), aboutData);
|
m_helpMenu = new KHelpMenu(MainWindow::instance(), aboutData);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|||||||
340
src/virtmic.cpp
340
src/virtmic.cpp
@@ -1,174 +1,194 @@
|
|||||||
#include "virtmic.h"
|
#include "virtmic.h"
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
|
|
||||||
QThread virtmicThread;
|
#include <rohrkabel/loop/main.hpp>
|
||||||
std::unique_ptr<pipewire::sender<Virtmic::set_target, Virtmic::terminate>>
|
#include <rohrkabel/registry/registry.hpp>
|
||||||
senderr;
|
|
||||||
std::unique_ptr<cr::receiver<Virtmic::new_targets>> receiverr;
|
|
||||||
|
|
||||||
void Virtmic::instance() {
|
namespace Virtmic {
|
||||||
if (!virtmicThread.isRunning()) {
|
|
||||||
auto [main_sender, main_receiver] = cr::channel<new_targets>();
|
const QStringList EXCLUDE_TARGETS{"Chromium input", "discord-screenaudio"};
|
||||||
auto [pw_sender, pw_receiver] = pipewire::channel<set_target, terminate>();
|
|
||||||
Virtmic *virtmic =
|
const std::string nullstr = "";
|
||||||
new Virtmic(std::move(pw_receiver), std::move(main_sender));
|
const std::string &getTarget(const pipewire::spa::dict &props) {
|
||||||
virtmic->moveToThread(&virtmicThread);
|
if (props.count("media.class") &&
|
||||||
virtmicThread.start();
|
props.at("media.class") == "Stream/Output/Audio") {
|
||||||
QMetaObject::invokeMethod(virtmic, "run");
|
if (props.count("application.name") && props.at("application.name") != "")
|
||||||
receiverr = std::make_unique<cr::receiver<Virtmic::new_targets>>(
|
return props.at("application.name");
|
||||||
std::move(main_receiver));
|
else if (props.count("application.process.binary") &&
|
||||||
senderr = std::make_unique<
|
props.at("application.process.binary") != "")
|
||||||
pipewire::sender<Virtmic::set_target, Virtmic::terminate>>(
|
return props.at("application.process.binary");
|
||||||
std::move(pw_sender));
|
else
|
||||||
|
return props.at("node.name");
|
||||||
|
} else
|
||||||
|
return nullstr;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString qGetTarget(const pipewire::spa::dict &props) {
|
||||||
|
return QString::fromStdString(getTarget(props));
|
||||||
|
}
|
||||||
|
|
||||||
|
QVector<QString> getTargets() {
|
||||||
|
auto main_loop = pipewire::main_loop();
|
||||||
|
auto context = pipewire::context(main_loop);
|
||||||
|
auto core = pipewire::core(context);
|
||||||
|
auto reg = pipewire::registry(core);
|
||||||
|
|
||||||
|
QVector<QString> targets;
|
||||||
|
|
||||||
|
auto reg_listener = reg.listen<pipewire::registry_listener>();
|
||||||
|
reg_listener.on<pipewire::registry_event::global>(
|
||||||
|
[&](const pipewire::global &global) {
|
||||||
|
if (global.type == pipewire::node::type) {
|
||||||
|
auto node = reg.bind<pipewire::node>(global.id);
|
||||||
|
auto info = node.info();
|
||||||
|
QString name = qGetTarget(info.props);
|
||||||
|
if (name != "" && !EXCLUDE_TARGETS.contains(name) &&
|
||||||
|
!targets.contains(name)) {
|
||||||
|
targets.append(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
core.update();
|
||||||
|
|
||||||
|
return targets;
|
||||||
|
}
|
||||||
|
|
||||||
|
void start(QString _target) {
|
||||||
|
std::map<std::uint32_t, pipewire::port> ports;
|
||||||
|
std::unique_ptr<pipewire::port> virt_fl, virt_fr;
|
||||||
|
|
||||||
|
std::map<std::uint32_t, pipewire::node_info> nodes;
|
||||||
|
std::map<std::uint32_t, pipewire::link_factory> links;
|
||||||
|
|
||||||
|
auto main_loop = pipewire::main_loop();
|
||||||
|
auto context = pipewire::context(main_loop);
|
||||||
|
auto core = pipewire::core(context);
|
||||||
|
auto reg = pipewire::registry(core);
|
||||||
|
|
||||||
|
auto link = [&](const std::string &target, pipewire::core &core) {
|
||||||
|
for (const auto &[port_id, port] : ports) {
|
||||||
|
if (!virt_fl || !virt_fr)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (links.count(port_id))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (port.info().direction == pipewire::port_direction::input)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (!port.info().props.count("node.id"))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
auto parent_id = std::stoul(port.info().props["node.id"]);
|
||||||
|
|
||||||
|
if (!nodes.count(parent_id))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
auto &parent = nodes.at(parent_id);
|
||||||
|
std::string name = getTarget(parent.props);
|
||||||
|
if (name == target ||
|
||||||
|
(target == "[All Desktop Audio]" &&
|
||||||
|
!EXCLUDE_TARGETS.contains(QString::fromStdString(name)))) {
|
||||||
|
auto fl = port.info().props["audio.channel"] == "FL";
|
||||||
|
links.emplace(
|
||||||
|
port_id,
|
||||||
|
core.create<pipewire::link_factory>(
|
||||||
|
{fl ? virt_fl->info().id : virt_fr->info().id, port_id}));
|
||||||
|
qDebug(virtmicLog) << QString("Link: %1:%2 -> %3")
|
||||||
|
.arg(QString::fromStdString(name))
|
||||||
|
.arg(port_id)
|
||||||
|
.arg(fl ? virt_fl->info().id
|
||||||
|
: virt_fr->info().id)
|
||||||
|
.toUtf8()
|
||||||
|
.data();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
std::string target = _target.toUtf8().toStdString();
|
||||||
|
|
||||||
|
auto virtual_mic = core.create("adapter",
|
||||||
|
{{"node.name", "discord-screenaudio-virtmic"},
|
||||||
|
{"media.class", "Audio/Source/Virtual"},
|
||||||
|
{"factory.name", "support.null-audio-sink"},
|
||||||
|
{"audio.channels", "2"},
|
||||||
|
{"audio.position", "FL,FR"}},
|
||||||
|
pipewire::node::type, pipewire::node::version,
|
||||||
|
pipewire::update_strategy::none);
|
||||||
|
|
||||||
|
if (target == "[None]") {
|
||||||
|
while (true) {
|
||||||
|
main_loop.run();
|
||||||
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
void Virtmic::setTarget(QString target) {
|
auto reg_events = reg.listen<pipewire::registry_listener>();
|
||||||
senderr.get()->send<Virtmic::set_target>({target});
|
reg_events.on<pipewire::registry_event::global>(
|
||||||
}
|
[&](const pipewire::global &global) {
|
||||||
|
if (global.type == pipewire::node::type) {
|
||||||
|
auto node = reg.bind<pipewire::node>(global.id);
|
||||||
|
auto info = node.info();
|
||||||
|
std::string name = getTarget(info.props);
|
||||||
|
if (name == nullstr)
|
||||||
|
return;
|
||||||
|
qDebug(virtmicLog) << QString("Added: %1")
|
||||||
|
.arg(QString::fromStdString(name))
|
||||||
|
.toUtf8()
|
||||||
|
.data();
|
||||||
|
|
||||||
void Virtmic::getTargets() { senderr.get()->send<Virtmic::get_targets>(); }
|
if (!nodes.count(global.id)) {
|
||||||
|
nodes.emplace(global.id, node.info());
|
||||||
|
link(target, core);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (global.type == pipewire::port::type) {
|
||||||
|
auto port = reg.bind<pipewire::port>(global.id);
|
||||||
|
auto info = port.info();
|
||||||
|
|
||||||
Virtmic::Virtmic(pipewire::receiver<set_target, terminate> receiver,
|
if (info.props.count("node.id")) {
|
||||||
cr::sender<new_targets> sender) {
|
auto node_id = std::stoul(info.props["node.id"]);
|
||||||
m_receiver = std::make_unique<pipewire::receiver<set_target, terminate>>(
|
|
||||||
std::move(receiver));
|
if (node_id == virtual_mic.id() &&
|
||||||
m_sender = std::make_unique<cr::sender<new_targets>>(std::move(sender));
|
info.direction == pipewire::port_direction::input) {
|
||||||
virtual_mic = std::make_unique<pipewire::proxy>(
|
if (info.props["audio.channel"] == "FL") {
|
||||||
std::move(*core.create("adapter",
|
virt_fl = std::make_unique<pipewire::port>(std::move(port));
|
||||||
{{"node.name", "discord-screenaudio-virtmic"},
|
} else {
|
||||||
{"media.class", "Audio/Source/Virtual"},
|
virt_fr = std::make_unique<pipewire::port>(std::move(port));
|
||||||
{"factory.name", "support.null-audio-sink"},
|
}
|
||||||
{"audio.channels", "2"},
|
} else {
|
||||||
{"audio.position", "FL,FR"}},
|
ports.emplace(global.id, std::move(port));
|
||||||
pipewire::node::type, pipewire::node::version,
|
}
|
||||||
pipewire::update_strategy::none)
|
|
||||||
.get()));
|
link(target, core);
|
||||||
metadata_listener.on<pipewire::registry_event::global>(
|
}
|
||||||
[&](const auto &global) { globalEvent(global); });
|
}
|
||||||
metadata_listener.on<pipewire::registry_event::global_removed>(
|
});
|
||||||
[&](const std::uint32_t id) { globalRemovedEvent(id); });
|
|
||||||
}
|
reg_events.on<pipewire::registry_event::global_removed>(
|
||||||
|
[&](const std::uint32_t id) {
|
||||||
|
if (nodes.count(id)) {
|
||||||
|
auto info = nodes.at(id);
|
||||||
|
std::string name = getTarget(info.props);
|
||||||
|
if (name == nullstr)
|
||||||
|
return;
|
||||||
|
qDebug(virtmicLog) << QString("Removed: %1")
|
||||||
|
.arg(QString::fromStdString(name))
|
||||||
|
.toUtf8()
|
||||||
|
.data();
|
||||||
|
nodes.erase(id);
|
||||||
|
}
|
||||||
|
if (ports.count(id)) {
|
||||||
|
ports.erase(id);
|
||||||
|
}
|
||||||
|
if (links.count(id)) {
|
||||||
|
links.erase(id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
void Virtmic::run() {
|
|
||||||
while (true) {
|
while (true) {
|
||||||
main_loop.run();
|
main_loop.run();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Virtmic::link() {
|
} // namespace Virtmic
|
||||||
for (const auto &[port_id, port] : ports) {
|
|
||||||
auto port_info = port.info();
|
|
||||||
if (!virt_fl || !virt_fr)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (links.count(port_id))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (port_info.direction == pipewire::port_direction::input)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (!port_info.props.count("node.id"))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
auto parent_id = std::stoul(port_info.props["node.id"]);
|
|
||||||
|
|
||||||
if (!nodes.count(parent_id))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
auto &parent = nodes[parent_id];
|
|
||||||
QString name;
|
|
||||||
if (parent.props.count("application.name") &&
|
|
||||||
parent.props["application.name"] != "")
|
|
||||||
name = QString::fromStdString(parent.props["application.name"]);
|
|
||||||
else
|
|
||||||
name = QString::fromStdString(parent.props["application.process.binary"]);
|
|
||||||
|
|
||||||
if (name == target ||
|
|
||||||
(target == "[All Desktop Audio]" && !EXCLUDE_TARGETS.contains(name))) {
|
|
||||||
auto fl = port_info.props["audio.channel"] == "FL";
|
|
||||||
links.emplace(port_id,
|
|
||||||
*core.create_simple<pipewire::link>(fl ? virt_fl->info().id
|
|
||||||
: virt_fr->info().id,
|
|
||||||
port_id)
|
|
||||||
.get());
|
|
||||||
qDebug(virtmicLog) << QString("Link: %1:%2 -> %3")
|
|
||||||
.arg(name)
|
|
||||||
.arg(port_id)
|
|
||||||
.arg(fl ? virt_fl->info().id
|
|
||||||
: virt_fr->info().id)
|
|
||||||
.toUtf8()
|
|
||||||
.data();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Virtmic::unlink() { links.clear(); }
|
|
||||||
|
|
||||||
void Virtmic::globalEvent(const pipewire::global &global) {
|
|
||||||
if (global.type == pipewire::node::type) {
|
|
||||||
auto node = *reg.bind<pipewire::node>(global.id).get();
|
|
||||||
auto info = node.info();
|
|
||||||
std::string name;
|
|
||||||
if (info.props.count("application.name") &&
|
|
||||||
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;
|
|
||||||
qDebug(virtmicLog) << QString("Added: %1")
|
|
||||||
.arg(QString::fromStdString(name))
|
|
||||||
.toUtf8()
|
|
||||||
.data();
|
|
||||||
|
|
||||||
if (!nodes.count(global.id)) {
|
|
||||||
nodes.emplace(global.id, node.info());
|
|
||||||
link();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (global.type == pipewire::port::type) {
|
|
||||||
auto port = *reg.bind<pipewire::port>(global.id).get();
|
|
||||||
auto info = port.info();
|
|
||||||
|
|
||||||
if (info.props.count("node.id")) {
|
|
||||||
auto node_id = std::stoul(info.props["node.id"]);
|
|
||||||
|
|
||||||
if (node_id == virtual_mic.get()->id() &&
|
|
||||||
info.direction == pipewire::port_direction::input) {
|
|
||||||
if (info.props["audio.channel"] == "FL") {
|
|
||||||
virt_fl = std::make_unique<pipewire::port>(std::move(port));
|
|
||||||
} else {
|
|
||||||
virt_fr = std::make_unique<pipewire::port>(std::move(port));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ports.emplace(global.id, std::move(port));
|
|
||||||
}
|
|
||||||
|
|
||||||
link();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Virtmic::globalRemovedEvent(const std::uint32_t id) {
|
|
||||||
if (nodes.count(id)) {
|
|
||||||
auto info = nodes.at(id);
|
|
||||||
std::string name;
|
|
||||||
if (info.props.count("application.name") &&
|
|
||||||
info.props["application.name"] != "")
|
|
||||||
name = info.props["application.name"];
|
|
||||||
else
|
|
||||||
name = info.props["application.process.binary"];
|
|
||||||
qDebug(virtmicLog) << QString("Removed: %1")
|
|
||||||
.arg(QString::fromStdString(name))
|
|
||||||
.toUtf8()
|
|
||||||
.data();
|
|
||||||
nodes.erase(id);
|
|
||||||
}
|
|
||||||
if (ports.count(id)) {
|
|
||||||
ports.erase(id);
|
|
||||||
}
|
|
||||||
if (links.count(id)) {
|
|
||||||
links.erase(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,63 +1,12 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <cr/channel.hpp>
|
#include <QString>
|
||||||
|
#include <QVector>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <rohrkabel/channel/channel.hpp>
|
|
||||||
#include <rohrkabel/main_loop.hpp>
|
|
||||||
#include <rohrkabel/registry/registry.hpp>
|
|
||||||
|
|
||||||
#include <QMap>
|
namespace Virtmic {
|
||||||
#include <QScopedPointer>
|
|
||||||
#include <QStringList>
|
|
||||||
#include <QThread>
|
|
||||||
|
|
||||||
class Virtmic : public QObject {
|
QVector<QString> getTargets();
|
||||||
Q_OBJECT
|
void start(QString _target);
|
||||||
public:
|
|
||||||
static void setTarget(QString target);
|
|
||||||
static void getTargets();
|
|
||||||
|
|
||||||
public:
|
} // namespace Virtmic
|
||||||
struct set_target {
|
|
||||||
QString name;
|
|
||||||
};
|
|
||||||
struct get_targets {};
|
|
||||||
struct terminate {};
|
|
||||||
struct new_targets {
|
|
||||||
QStringList targets;
|
|
||||||
};
|
|
||||||
|
|
||||||
protected:
|
|
||||||
static void instance();
|
|
||||||
|
|
||||||
protected:
|
|
||||||
Virtmic(pipewire::receiver<set_target, terminate> receiver,
|
|
||||||
cr::sender<new_targets> sender);
|
|
||||||
void run();
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::unique_ptr<pipewire::receiver<set_target, terminate>> m_receiver;
|
|
||||||
std::unique_ptr<cr::sender<new_targets>> m_sender;
|
|
||||||
|
|
||||||
const QStringList EXCLUDE_TARGETS{"Chromium input", "discord-screenaudio"};
|
|
||||||
QString target;
|
|
||||||
|
|
||||||
pipewire::main_loop main_loop = pipewire::main_loop();
|
|
||||||
pipewire::context context = pipewire::context(main_loop);
|
|
||||||
pipewire::core core = pipewire::core(context);
|
|
||||||
pipewire::registry reg = pipewire::registry(core);
|
|
||||||
|
|
||||||
pipewire::registry_listener metadata_listener =
|
|
||||||
reg.listen<pipewire::registry_listener>();
|
|
||||||
std::unique_ptr<pipewire::proxy> virtual_mic;
|
|
||||||
|
|
||||||
std::map<uint32_t, pipewire::port> ports;
|
|
||||||
std::unique_ptr<pipewire::port> virt_fl, virt_fr;
|
|
||||||
std::map<uint32_t, pipewire::node_info> nodes;
|
|
||||||
std::map<uint32_t, pipewire::link> links;
|
|
||||||
|
|
||||||
void link();
|
|
||||||
void unlink();
|
|
||||||
void globalEvent(const pipewire::global &global);
|
|
||||||
void globalRemovedEvent(const std::uint32_t id);
|
|
||||||
};
|
|
||||||
|
|||||||
Submodule submodules/channel deleted from 6977815409
Submodule submodules/rohrkabel updated: 8a7705be07...04bfb921c4
2
uninstall.sh
Executable file
2
uninstall.sh
Executable file
@@ -0,0 +1,2 @@
|
|||||||
|
flatpak remove lol.deadzone.discord-awesomeaudio
|
||||||
|
rm ~/.local/share/applications/discord-awesomeaudio.desktop
|
||||||
Reference in New Issue
Block a user