Compare commits

..

18 Commits

Author SHA1 Message Date
Wizzard
58c3ac0b71 Scripts 2023-07-11 19:50:07 -04:00
Wizzard
2858eb3854 Scruots 2023-07-11 19:49:39 -04:00
Malte Jürgens
94b27f5b6a implement ctrl+q keybind 2023-06-23 17:48:54 +02:00
Malte Jürgens
8a6f49b949 Merge pull request #149 from Vitalya-code/master
Move two functions into separate file
2023-04-27 18:04:34 +00:00
Malte Jürgens
47fd620876 fix formatting 2023-04-27 20:04:04 +02:00
Vitalya
c15250498b Move server function to the bottom and change "utils" file name to "localserver" 2023-04-27 19:52:28 +03:00
Vitalya
6f0303206e Move two functions into separate file 2023-04-27 17:21:03 +03:00
Malte Jürgens
6e86647c95 Merge pull request #148 from Vitalya-code/master
Shows error message to user if discord is running
2023-04-25 16:33:21 +00:00
Vitalya
798fb3d5e4 Minor change in error message
Change "Discord" to "discord-screenaudio" in error message
2023-04-25 15:07:25 +03:00
Malte Jürgens
6c9b76ed90 format code 2023-04-24 22:41:36 +02:00
Vitalya
b582584c69 Shows error message to user if discord is running 2023-04-24 19:58:56 +03:00
Malte Jürgens
7b6e8fc473 fix segfault in console code 2023-04-18 21:31:22 +02:00
Malte Jürgens
8f0a810539 virtmic: also use nodes which only have a node.name 2023-04-17 15:42:19 +02:00
Malte Jürgens
a6eb82948f add dnf dependencies 2023-04-17 09:51:10 +02:00
Malte Jürgens
24727f398a Merge pull request #133 from mensinda/latin2utf8
Do not convert to Latin1 and use UTF-8 instead
2023-04-03 17:26:55 +00:00
Daniel Mensinger
c43e9953a5 Do not convert to Latin1 and use UTF-8 instead 2023-04-03 17:20:53 +02:00
Malte Jürgens
08cb713e8c add arRPC component to about 2023-03-23 23:14:00 +01:00
Malte Jürgens
27faed4a3a set minimum window size 2023-03-19 14:44:25 +01:00
20 changed files with 375 additions and 252 deletions

23
.gitignore vendored
View File

@@ -1,3 +1,20 @@
/build
.vscode
/submodules/arrpc
# Ignore build and output directories
/build/
/.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
View File

@@ -1,6 +1,3 @@
[submodule "submodules/rohrkabel"]
path = submodules/rohrkabel
url = https://github.com/Soundux/rohrkabel
[submodule "submodules/channel"]
path = submodules/channel
url = https://github.com/Soundux/channel.git

View File

@@ -45,6 +45,7 @@ set(discord-screenaudio_SRC
src/log.cpp
src/userscript.cpp
src/centralwidget.cpp
src/localserver.cpp
resources.qrc
)
@@ -63,20 +64,15 @@ if(GIT_FOUND AND EXISTS "${PROJECT_SOURCE_DIR}/.git")
endif()
endif()
function(add_git_subdirectory SUBMODULE)
if(NOT EXISTS "${PROJECT_SOURCE_DIR}/submodules/${SUBMODULE}/CMakeLists.txt")
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()
if(NOT EXISTS "${PROJECT_SOURCE_DIR}/submodules/rohrkabel/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`.")
endif()
add_subdirectory(submodules/${SUBMODULE})
endfunction()
add_git_subdirectory(rohrkabel)
add_git_subdirectory(channel)
add_subdirectory(submodules/rohrkabel)
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)
target_link_libraries(discord-screenaudio KF5::Notifications)

View File

@@ -62,6 +62,9 @@ You have multiple options:
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`
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
First, clone the repository:

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

28
install.sh Executable file
View 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

View 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;"
}
}

View File

@@ -282,6 +282,7 @@ void DiscordPage::javaScriptConsoleMessage(
ansi += "\033[" + cssAnsiColorMap[color] + "m";
}
}
if (endOfStyles < lines.length())
qDebug(discordLog) << (ansi + lines[0].trimmed() + "\033[0m " +
lines[endOfStyles].trimmed())
.toUtf8()

22
src/localserver.cpp Normal file
View 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
View 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);

View File

@@ -1,3 +1,4 @@
#include "localserver.h"
#include "mainwindow.h"
#include "virtmic.h"
@@ -7,10 +8,14 @@
#include <QApplication>
#include <QCommandLineParser>
#include <QLocalServer>
#include <QLocalSocket>
#include <QLoggingCategory>
#include <QMessageBox>
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
QApplication::setApplicationName("discord-screenaudio");
QApplication::setWindowIcon(
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");
parser.addHelpOption();
parser.addVersionOption();
QCommandLineOption virtmicOption("virtmic", "Start the Virtual Microphone",
"target");
parser.addOption(virtmicOption);
QCommandLineOption degubOption("remote-debugging",
"Open Chromium Remote Debugging on port 9222");
parser.addOption(degubOption);
@@ -33,6 +41,10 @@ int main(int argc, char *argv[]) {
parser.process(app);
if (parser.isSet(virtmicOption)) {
Virtmic::start(parser.value(virtmicOption));
}
qputenv("QTWEBENGINE_CHROMIUM_FLAGS",
"--enable-features=WebRTCPipeWireCapturer " +
qgetenv("QTWEBENGINE_CHROMIUM_FLAGS"));
@@ -43,6 +55,20 @@ int main(int argc, char *argv[]) {
qgetenv("QTWEBENGINE_CHROMIUM_FLAGS"));
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();
return app.exec();

View File

@@ -12,6 +12,7 @@
#include <QGridLayout>
#include <QLabel>
#include <QPushButton>
#include <QShortcut>
#include <QSpacerItem>
#include <QThread>
#include <QTimer>
@@ -30,6 +31,9 @@ MainWindow::MainWindow(bool useNotifySend, QWidget *parent)
m_centralWidget = new CentralWidget(this);
setCentralWidget(m_centralWidget);
setupTrayIcon();
setMinimumSize(800, 300);
connect(new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_Q), this),
&QShortcut::activated, this, &MainWindow::toggleOrCloseWindow);
if (m_settings->contains("geometry")) {
restoreGeometry(m_settings->value("geometry").toByteArray());
} else {
@@ -78,12 +82,7 @@ void MainWindow::setupTrayIcon() {
connect(m_trayIcon, &QSystemTrayIcon::activated, [this](auto reason) {
if (reason == QSystemTrayIcon::Trigger) {
if (isVisible()) {
hide();
} else {
show();
activateWindow();
}
toggleOrCloseWindow();
}
});
}
@@ -130,3 +129,15 @@ MainWindow *MainWindow::instance() { return m_instance; }
CentralWidget *MainWindow::centralWidget() {
return instance()->m_centralWidget;
};
void MainWindow::toggleOrCloseWindow() {
if (isVisible()) {
if (m_trayIcon == nullptr)
QApplication::quit();
else
hide();
} else {
show();
activateWindow();
}
}

View File

@@ -1,7 +1,6 @@
#pragma once
#include "centralwidget.h"
#include "virtmic.h"
#include <QMainWindow>
#include <QMenu>
@@ -37,4 +36,5 @@ private:
public Q_SLOTS:
void setTrayIcon(bool enabled);
void fullScreenRequested(QWebEngineFullScreenRequest fullScreenRequest);
void toggleOrCloseWindow();
};

View File

@@ -1,5 +1,4 @@
#include "streamdialog.h"
#include "mainwindow.h"
#include "virtmic.h"
#include <QComboBox>

View File

@@ -42,12 +42,12 @@ void UserScript::setupHelpMenu() {
aboutData.addCredit(
"Curve", "For creating the Rohrkabel library used in this project.",
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");
aboutData.addComponent(
"Soundux/channel ",
"A C++ implementation of Rust's std::sync::mpsc::channel", nullptr,
"https://github.com/Soundux/channel");
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);
#endif
}

View File

@@ -1,99 +1,101 @@
#include "virtmic.h"
#include "log.h"
QThread virtmicThread;
std::unique_ptr<pipewire::sender<Virtmic::set_target, Virtmic::terminate>>
senderr;
std::unique_ptr<cr::receiver<Virtmic::new_targets>> receiverr;
#include <rohrkabel/loop/main.hpp>
#include <rohrkabel/registry/registry.hpp>
void Virtmic::instance() {
if (!virtmicThread.isRunning()) {
auto [main_sender, main_receiver] = cr::channel<new_targets>();
auto [pw_sender, pw_receiver] = pipewire::channel<set_target, terminate>();
Virtmic *virtmic =
new Virtmic(std::move(pw_receiver), std::move(main_sender));
virtmic->moveToThread(&virtmicThread);
virtmicThread.start();
QMetaObject::invokeMethod(virtmic, "run");
receiverr = std::make_unique<cr::receiver<Virtmic::new_targets>>(
std::move(main_receiver));
senderr = std::make_unique<
pipewire::sender<Virtmic::set_target, Virtmic::terminate>>(
std::move(pw_sender));
namespace Virtmic {
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() {
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);
}
}
void Virtmic::setTarget(QString target) {
senderr.get()->send<Virtmic::set_target>({target});
}
void Virtmic::getTargets() { senderr.get()->send<Virtmic::get_targets>(); }
Virtmic::Virtmic(pipewire::receiver<set_target, terminate> receiver,
cr::sender<new_targets> sender) {
m_receiver = std::make_unique<pipewire::receiver<set_target, terminate>>(
std::move(receiver));
m_sender = std::make_unique<cr::sender<new_targets>>(std::move(sender));
virtual_mic = std::make_unique<pipewire::proxy>(
std::move(*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)
.get()));
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); });
}
void Virtmic::run() {
while (true) {
main_loop.run();
}
});
core.update();
return targets;
}
void Virtmic::link() {
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) {
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)
if (port.info().direction == pipewire::port_direction::input)
continue;
if (!port_info.props.count("node.id"))
if (!port.info().props.count("node.id"))
continue;
auto parent_id = std::stoul(port_info.props["node.id"]);
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"]);
auto &parent = nodes.at(parent_id);
std::string name = getTarget(parent.props);
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());
(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(name)
.arg(QString::fromStdString(name))
.arg(port_id)
.arg(fl ? virt_fl->info().id
: virt_fr->info().id)
@@ -101,21 +103,34 @@ void Virtmic::link() {
.data();
}
}
}
};
void Virtmic::unlink() { links.clear(); }
std::string target = _target.toUtf8().toStdString();
void Virtmic::globalEvent(const pipewire::global &global) {
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;
}
auto reg_events = reg.listen<pipewire::registry_listener>();
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).get();
auto node = reg.bind<pipewire::node>(global.id);
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
std::string name = getTarget(info.props);
if (name == nullstr)
return;
qDebug(virtmicLog) << QString("Added: %1")
.arg(QString::fromStdString(name))
@@ -124,17 +139,17 @@ void Virtmic::globalEvent(const pipewire::global &global) {
if (!nodes.count(global.id)) {
nodes.emplace(global.id, node.info());
link();
link(target, core);
}
}
if (global.type == pipewire::port::type) {
auto port = *reg.bind<pipewire::port>(global.id).get();
auto port = reg.bind<pipewire::port>(global.id);
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() &&
if (node_id == virtual_mic.id() &&
info.direction == pipewire::port_direction::input) {
if (info.props["audio.channel"] == "FL") {
virt_fl = std::make_unique<pipewire::port>(std::move(port));
@@ -145,20 +160,18 @@ void Virtmic::globalEvent(const pipewire::global &global) {
ports.emplace(global.id, std::move(port));
}
link();
link(target, core);
}
}
}
});
void Virtmic::globalRemovedEvent(const std::uint32_t 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;
if (info.props.count("application.name") &&
info.props["application.name"] != "")
name = info.props["application.name"];
else
name = info.props["application.process.binary"];
std::string name = getTarget(info.props);
if (name == nullstr)
return;
qDebug(virtmicLog) << QString("Removed: %1")
.arg(QString::fromStdString(name))
.toUtf8()
@@ -171,4 +184,11 @@ void Virtmic::globalRemovedEvent(const std::uint32_t id) {
if (links.count(id)) {
links.erase(id);
}
});
while (true) {
main_loop.run();
}
}
} // namespace Virtmic

View File

@@ -1,63 +1,12 @@
#pragma once
#include <cr/channel.hpp>
#include <QString>
#include <QVector>
#include <iostream>
#include <rohrkabel/channel/channel.hpp>
#include <rohrkabel/main_loop.hpp>
#include <rohrkabel/registry/registry.hpp>
#include <QMap>
#include <QScopedPointer>
#include <QStringList>
#include <QThread>
namespace Virtmic {
class Virtmic : public QObject {
Q_OBJECT
public:
static void setTarget(QString target);
static void getTargets();
QVector<QString> getTargets();
void start(QString _target);
public:
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);
};
} // namespace Virtmic

2
uninstall.sh Executable file
View File

@@ -0,0 +1,2 @@
flatpak remove lol.deadzone.discord-awesomeaudio
rm ~/.local/share/applications/discord-awesomeaudio.desktop