Compare commits
	
		
			3 Commits
		
	
	
		
			v1.8.0
			...
			virtmic-re
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | e6449ad807 | ||
|  | e919e3ea25 | ||
|  | f31901073e | 
							
								
								
									
										3
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
								
							| @@ -1,3 +1,6 @@ | ||||
| [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 | ||||
|   | ||||
| @@ -63,15 +63,20 @@ if(GIT_FOUND AND EXISTS "${PROJECT_SOURCE_DIR}/.git") | ||||
|   endif() | ||||
| 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() | ||||
| 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() | ||||
|  | ||||
| add_subdirectory(submodules/rohrkabel) | ||||
|   add_subdirectory(submodules/${SUBMODULE}) | ||||
| endfunction() | ||||
|  | ||||
| add_git_subdirectory(rohrkabel) | ||||
| add_git_subdirectory(channel) | ||||
|  | ||||
| add_executable(discord-screenaudio ${discord-screenaudio_SRC}) | ||||
|  | ||||
| target_link_libraries(discord-screenaudio Qt::Widgets Qt::WebEngineWidgets rohrkabel) | ||||
| target_link_libraries(discord-screenaudio Qt::Widgets Qt::WebEngineWidgets rohrkabel channel) | ||||
|  | ||||
| if(KF5Notifications_FOUND) | ||||
|   target_link_libraries(discord-screenaudio KF5::Notifications) | ||||
|   | ||||
| @@ -24,9 +24,6 @@ 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); | ||||
| @@ -36,10 +33,6 @@ 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")); | ||||
|   | ||||
| @@ -30,7 +30,6 @@ MainWindow::MainWindow(bool useNotifySend, QWidget *parent) | ||||
|   m_centralWidget = new CentralWidget(this); | ||||
|   setCentralWidget(m_centralWidget); | ||||
|   setupTrayIcon(); | ||||
|   setMinimumSize(800, 300); | ||||
|   if (m_settings->contains("geometry")) { | ||||
|     restoreGeometry(m_settings->value("geometry").toByteArray()); | ||||
|   } else { | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "centralwidget.h" | ||||
| #include "virtmic.h" | ||||
|  | ||||
| #include <QMainWindow> | ||||
| #include <QMenu> | ||||
|   | ||||
| @@ -1,4 +1,5 @@ | ||||
| #include "streamdialog.h" | ||||
| #include "mainwindow.h" | ||||
| #include "virtmic.h" | ||||
|  | ||||
| #include <QComboBox> | ||||
|   | ||||
| @@ -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.3", | ||||
|   aboutData.addComponent("Rohrkabel", "A C++ RAII Pipewire-API Wrapper", "1.5", | ||||
|                          "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"); | ||||
|   aboutData.addComponent( | ||||
|       "Soundux/channel ", | ||||
|       "A C++ implementation of Rust's std::sync::mpsc::channel", nullptr, | ||||
|       "https://github.com/Soundux/channel"); | ||||
|   m_helpMenu = new KHelpMenu(MainWindow::instance(), aboutData); | ||||
| #endif | ||||
| } | ||||
|   | ||||
							
								
								
									
										342
									
								
								src/virtmic.cpp
									
									
									
									
									
								
							
							
						
						
									
										342
									
								
								src/virtmic.cpp
									
									
									
									
									
								
							| @@ -1,196 +1,174 @@ | ||||
| #include "virtmic.h" | ||||
| #include "log.h" | ||||
|  | ||||
| #include <rohrkabel/loop/main.hpp> | ||||
| #include <rohrkabel/registry/registry.hpp> | ||||
| QThread virtmicThread; | ||||
| std::unique_ptr<pipewire::sender<Virtmic::set_target, Virtmic::terminate>> | ||||
|     senderr; | ||||
| std::unique_ptr<cr::receiver<Virtmic::new_targets>> receiverr; | ||||
|  | ||||
| namespace Virtmic { | ||||
|  | ||||
| const QStringList EXCLUDE_TARGETS{"Chromium input", "discord-screenaudio"}; | ||||
|  | ||||
| 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; | ||||
|           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) && | ||||
|               !targets.contains(name)) { | ||||
|             targets.append(name); | ||||
|           } | ||||
|         } | ||||
|       }); | ||||
|   core.update(); | ||||
|  | ||||
|   return targets; | ||||
| 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)); | ||||
|   } | ||||
| } | ||||
|  | ||||
| void start(QString _target) { | ||||
|   std::map<std::uint32_t, pipewire::port> ports; | ||||
|   std::unique_ptr<pipewire::port> virt_fl, virt_fr; | ||||
| void Virtmic::setTarget(QString target) { | ||||
|   senderr.get()->send<Virtmic::set_target>({target}); | ||||
| } | ||||
|  | ||||
|   std::map<std::uint32_t, pipewire::node_info> nodes; | ||||
|   std::map<std::uint32_t, pipewire::link_factory> links; | ||||
| void Virtmic::getTargets() { senderr.get()->send<Virtmic::get_targets>(); } | ||||
|  | ||||
|   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; | ||||
|       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 || | ||||
|           (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; | ||||
|   } | ||||
|  | ||||
|   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); | ||||
|           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(target, core); | ||||
|           } | ||||
|         } | ||||
|         if (global.type == pipewire::port::type) { | ||||
|           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.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(target, core); | ||||
|           } | ||||
|         } | ||||
|       }); | ||||
|  | ||||
|   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"]; | ||||
|           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); | ||||
|         } | ||||
|       }); | ||||
| 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(); | ||||
|   } | ||||
| } | ||||
|  | ||||
| } // namespace Virtmic | ||||
| void Virtmic::link() { | ||||
|   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,12 +1,63 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include <QString> | ||||
| #include <QVector> | ||||
| #include <cr/channel.hpp> | ||||
| #include <iostream> | ||||
| #include <rohrkabel/channel/channel.hpp> | ||||
| #include <rohrkabel/main_loop.hpp> | ||||
| #include <rohrkabel/registry/registry.hpp> | ||||
|  | ||||
| namespace Virtmic { | ||||
| #include <QMap> | ||||
| #include <QScopedPointer> | ||||
| #include <QStringList> | ||||
| #include <QThread> | ||||
|  | ||||
| QVector<QString> getTargets(); | ||||
| void start(QString _target); | ||||
| class Virtmic : public QObject { | ||||
|   Q_OBJECT | ||||
| public: | ||||
|   static void setTarget(QString target); | ||||
|   static void getTargets(); | ||||
|  | ||||
| } // namespace Virtmic | ||||
| 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); | ||||
| }; | ||||
|   | ||||
							
								
								
									
										1
									
								
								submodules/channel
									
									
									
									
									
										Submodule
									
								
							
							
								
								
								
								
								
							
						
						
									
										1
									
								
								submodules/channel
									
									
									
									
									
										Submodule
									
								
							 Submodule submodules/channel added at 6977815409
									
								
							 Submodule submodules/rohrkabel updated: 04bfb921c4...8a7705be07
									
								
							
		Reference in New Issue
	
	Block a user