Compare commits

..

3 Commits

Author SHA1 Message Date
Malte Jürgens e6449ad807
work 2023-03-19 14:39:15 +01:00
Malte Jürgens e919e3ea25
Merge branch 'master' into virtmic-rewrite 2023-03-17 13:58:08 +01:00
Malte Jürgens f31901073e
update rohrkabel to v1.5 2023-02-24 15:14:38 +01:00
32 changed files with 420 additions and 1117 deletions

23
.gitignore vendored
View File

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

View File

@ -45,15 +45,6 @@ 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
src/centralwidget.h
src/discordpage.h
src/localserver.h
src/log.h
src/mainwindow.h
src/streamdialog.h
src/userscript.h
src/virtmic.h
resources.qrc resources.qrc
) )
@ -72,15 +63,20 @@ if(GIT_FOUND AND EXISTS "${PROJECT_SOURCE_DIR}/.git")
endif() endif()
endif() endif()
if(NOT EXISTS "${PROJECT_SOURCE_DIR}/submodules/rohrkabel/CMakeLists.txt") function(add_git_subdirectory SUBMODULE)
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`.") if(NOT EXISTS "${PROJECT_SOURCE_DIR}/submodules/${SUBMODULE}/CMakeLists.txt")
endif() 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}) 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) if(KF5Notifications_FOUND)
target_link_libraries(discord-screenaudio KF5::Notifications) target_link_libraries(discord-screenaudio KF5::Notifications)

View File

@ -62,9 +62,6 @@ 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:

View File

@ -28,7 +28,7 @@ function setGetDisplayMedia(video = true, overrideArgs = undefined) {
var id; var id;
try { try {
let myDiscordAudioSink = await getAudioDevice( let myDiscordAudioSink = await getAudioDevice(
"discord-awesomeaudio-virtmic" "discord-screenaudio-virtmic"
); );
id = myDiscordAudioSink.deviceId; id = myDiscordAudioSink.deviceId;
} catch (error) { } catch (error) {
@ -240,7 +240,7 @@ function main() {
} else { } else {
aboutEl = document.createElement("div"); aboutEl = document.createElement("div");
} }
aboutEl.innerText = `discord-awesomeaudio ${userscript.version}`; aboutEl.innerText = `discord-screenaudio ${userscript.version}`;
aboutEl.style.fontSize = "12px"; aboutEl.style.fontSize = "12px";
aboutEl.style.color = "var(--text-muted)"; aboutEl.style.color = "var(--text-muted)";
aboutEl.style.textTransform = "none"; aboutEl.style.textTransform = "none";
@ -319,7 +319,7 @@ function main() {
const title = document.createElement("h2"); const title = document.createElement("h2");
title.className = title.className =
"h1-3iMExa title-lXcL8p defaultColor-3Olr-9 defaultMarginh1-1UYutH"; "h1-3iMExa title-lXcL8p defaultColor-3Olr-9 defaultMarginh1-1UYutH";
title.innerText = "discord-awesomeaudio"; title.innerText = "discord-screenaudio";
section.appendChild(title); section.appendChild(title);
section.appendChild( section.appendChild(
@ -340,19 +340,9 @@ function main() {
// }) // })
// ); // );
section.appendChild( section.appendChild(
createSwitch( createSwitch(
"Allow audio from your mic (and audio you pass through) to become stereo", "Move discord-screenaudio to the system tray instead of closing",
await userscript.getBoolPref("stereoMic", false),
(enabled) => {
userscript.setStereoMic(enabled);
}
)
);
section.appendChild(
createSwitch(
"Move discord-awesomeaudio to the system tray instead of closing",
await userscript.getBoolPref("trayIcon", false), await userscript.getBoolPref("trayIcon", false),
(enabled) => { (enabled) => {
userscript.setTrayIcon(enabled); userscript.setTrayIcon(enabled);
@ -362,7 +352,7 @@ function main() {
section.appendChild( section.appendChild(
createSwitch( createSwitch(
"Start discord-awesomeaudio hidden to tray", "Start discord-screenaudio hidden to tray",
await userscript.getPref("startHidden", false), await userscript.getPref("startHidden", false),
(hidden) => { (hidden) => {
userscript.setPref("startHidden", hidden); userscript.setPref("startHidden", hidden);

View File

@ -1,85 +0,0 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2022 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
/// <reference path="../src/modules.d.ts" />
/// <reference path="../src/globals.d.ts" />
import monacoHtml from "~fileContent/../src/components/monacoWin.html";
import * as DataStore from "../src/api/DataStore";
import { debounce } from "../src/utils";
import { getTheme, Theme } from "../src/utils/discord";
// Discord deletes this so need to store in variable
const { localStorage } = window;
// listeners for ipc.on
const cssListeners = new Set<(css: string) => void>();
const NOOP = () => { };
const NOOP_ASYNC = async () => { };
const setCssDebounced = debounce((css: string) => VencordNative.quickCss.set(css));
// probably should make this less cursed at some point
window.VencordNative = {
native: {
getVersions: () => ({}),
openExternal: async (url) => void open(url, "_blank")
},
updater: {
getRepo: async () => ({ ok: true, value: "https://github.com/Vendicated/Vencord" }),
getUpdates: async () => ({ ok: true, value: [] }),
update: async () => ({ ok: true, value: false }),
rebuild: async () => ({ ok: true, value: true }),
},
quickCss: {
get: () => DataStore.get("VencordQuickCss").then(s => s ?? ""),
set: async (css: string) => {
await DataStore.set("VencordQuickCss", css);
cssListeners.forEach(l => l(css));
},
addChangeListener(cb) {
cssListeners.add(cb);
},
openFile: NOOP_ASYNC,
async openEditor() {
const features = `popup,width=${Math.min(window.innerWidth, 1000)},height=${Math.min(window.innerHeight, 1000)}`;
const win = open("about:blank", "VencordQuickCss", features);
if (!win) {
alert("Failed to open QuickCSS popup. Make sure to allow popups!");
return;
}
win.setCss = setCssDebounced;
win.getCurrentCss = () => VencordNative.quickCss.get();
win.getTheme = () =>
getTheme() === Theme.Light
? "vs-light"
: "vs-dark";
win.document.write(monacoHtml);
},
},
settings: {
get: () => localStorage.getItem("VencordSettings") || "{}",
set: async (s: string) => localStorage.setItem("VencordSettings", s),
getSettingsDir: async () => "LocalStorage"
}
};

View File

@ -1,14 +0,0 @@
import definePlugin from "../utils/types";
export default definePlugin({
name: "discord-awesomeaudio",
authors: [
{
name: "retard",
id: 205966226709676099n,
},
],
required: true,
description: "UI patches for discord-screenaudio.",
patches: [],
});

View File

@ -1,15 +0,0 @@
--- a/src/components/VencordSettings/VencordTab.tsx
+++ b/src/components/VencordSettings/VencordTab.tsx
@@ -87,10 +87,10 @@ function VencordSettings() {
<Card className={cl("quick-actions-card")}>
{IS_WEB ? (
<Button
- onClick={() => require("../Monaco").launchMonacoEditor()}
+ onClick={() => VencordNative.ipc.send(IpcEvents.OPEN_EXTERNAL, settingsDir)}
size={Button.Sizes.SMALL}
disabled={settingsDir === "Loading..."}>
- Open QuickCSS File
+ Launch Directory
</Button>
) : (
<React.Fragment>

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

View File

@ -1,42 +0,0 @@
#!/bin/bash
is_package_installed() {
if command -v "$1" >/dev/null 2>&1; then
return 0
else
return 1
fi
}
if [[ -f /etc/os-release ]]; then
. /etc/os-release
if [[ "$ID" == "ubuntu" || "$ID_LIKE" == "ubuntu" ]]; then
if ! is_package_installed flatpak-builder; then
sudo apt update
sudo apt install -y flatpak-builder
fi
elif [[ "$ID" == "fedora" || "$ID_LIKE" == "fedora" ]]; then
if ! is_package_installed flatpak-builder; then
sudo dnf install -y flatpak-builder
fi
elif [[ "$ID" == "void" ]]; then
if ! is_package_installed flatpak-builder; then
sudo xbps-install -Sy flatpak-builder
fi
fi
fi
flatpak install flathub org.kde.Sdk//5.15-22.08
flatpak install flathub io.qt.qtwebengine.BaseApp//5.15-22.08
flatpak-builder build-dir lol.deadzone.discord-awesomeaudio.json --install --user --force-clean
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
chmod +x ~/.local/share/applications/discord-awesomeaudio.desktop

View File

@ -1,44 +0,0 @@
{
"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

@ -1,12 +1,9 @@
<!DOCTYPE RCC>
<RCC> <RCC>
<qresource prefix="/"> <qresource>
<file>assets/userscript.js</file> <file>assets/userscript.js</file>
<file>assets/arrpc_bridge_mod.js</file> <file>assets/arrpc_bridge_mod.js</file>
<file>assets/arrpc.js</file> <file>assets/arrpc.js</file>
<file>assets/de.shorsh.discord-screenaudio.png</file> <file>assets/de.shorsh.discord-screenaudio.png</file>
<file>assets/vencord/vencord.js</file> </qresource>
<file>assets/vencord/settings.patch</file> </RCC>
<file>assets/vencord/plugin.js</file>
<file>assets/vencord/VencordNativeStub.ts</file>
</qresource>
</RCC>

View File

@ -1,39 +0,0 @@
#!/usr/bin/bash
set -e
cd "$(dirname "$0")/../submodules"
echo_status() {
echo
echo
echo "-> $1..."
}
if [ ! -d "Vencord" ]; then
echo_status "Cloning Vencord"
git clone https://github.com/Vendicated/Vencord.git
cd Vencord
else
echo_status "Fetching Vencord changes"
cd Vencord
git fetch
fi
echo_status "Checking out latest commit"
git reset --hard HEAD
git checkout main
#git reset --hard devbuild
echo_status "Installing dependencies"
pnpm i
echo_status "Patching Vencord"
cp -v ../../assets/vencord/plugin.js ./src/plugins/discord-screenaudio.js
cp -v ../../assets/vencord/VencordNativeStub.ts ./browser/VencordNativeStub.ts
patch -p1 -i ../../assets/vencord/settings.patch
echo_status "Building Vencord"
pnpm run buildWeb
echo_status "Copying built file"
cp -v ./dist/browser.js ../../assets/vencord/vencord.js

View File

@ -7,7 +7,6 @@
#include <QWebEngineScript> #include <QWebEngineScript>
#include <QWebEngineScriptCollection> #include <QWebEngineScriptCollection>
#include <QWebEngineSettings> #include <QWebEngineSettings>
#include <QWebEngineView>
CentralWidget::CentralWidget(QWidget *parent) : QWidget(parent) { CentralWidget::CentralWidget(QWidget *parent) : QWidget(parent) {
setStyleSheet("background-color:#313338;"); setStyleSheet("background-color:#313338;");

View File

@ -1,5 +1,4 @@
#ifndef CENTRALWIDGET_H #pragma once
#define CENTRALWIDGET_H
#include <QLabel> #include <QLabel>
#include <QVBoxLayout> #include <QVBoxLayout>
@ -8,28 +7,23 @@
#include <QWebEngineView> #include <QWebEngineView>
#include <QWidget> #include <QWidget>
#ifdef KNOTIFICATIONS
constexpr bool USE_KF5_NOTIFICATIONS = true;
#else
constexpr bool USE_KF5_NOTIFICATIONS = false;
#endif
class CentralWidget : public QWidget { class CentralWidget : public QWidget {
Q_OBJECT Q_OBJECT
public: public:
CentralWidget(QWidget *parent = nullptr); CentralWidget(QWidget *parent = nullptr);
public slots:
void setLoadingIndicator(QString text);
private: private:
void setupWebView(); void setupWebView();
QVBoxLayout *m_layout; QVBoxLayout *m_layout;
QWebEngineView *m_webView; QWebEngineView *m_webView;
QLabel *m_loadingLabel; #ifdef KNOTIFICATIONS
bool m_useKF5Notifications; bool m_useKF5Notifications = true;
}; #else
bool m_useKF5Notifications = false;
#endif
QLabel *m_loadingLabel = nullptr;
#endif // CENTRALWIDGET_H public Q_SLOTS:
void setLoadingIndicator(QString text);
};

View File

@ -36,16 +36,7 @@ DiscordPage::DiscordPage(QWidget *parent) : QWebEnginePage(parent) {
injectFile(&DiscordPage::injectScript, "userscript.js", injectFile(&DiscordPage::injectScript, "userscript.js",
":/assets/userscript.js"); ":/assets/userscript.js");
QFile vencord(":/assets/vencord/vencord.js");
if (!vencord.open(QIODevice::ReadOnly))
qFatal("Failed to load vencord source with error: %s",
vencord.errorString().toLatin1().constData());
injectScript(
"vencord.js",
QString("window.discordScreenaudioVencordSettings = `%1`; %2")
.arg(m_userScript.vencordSend("VencordGetSettings", {}).toString(),
vencord.readAll()));
vencord.close();
setupUserStyles(); setupUserStyles();
setupArrpc(); setupArrpc();
} }
@ -291,11 +282,10 @@ void DiscordPage::javaScriptConsoleMessage(
ansi += "\033[" + cssAnsiColorMap[color] + "m"; ansi += "\033[" + cssAnsiColorMap[color] + "m";
} }
} }
if (endOfStyles < lines.length()) qDebug(discordLog) << (ansi + lines[0].trimmed() + "\033[0m " +
qDebug(discordLog) << (ansi + lines[0].trimmed() + "\033[0m " + lines[endOfStyles].trimmed())
lines[endOfStyles].trimmed()) .toUtf8()
.toUtf8() .constData();
.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();
} }

View File

@ -1,22 +0,0 @@
#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();
}

View File

@ -1,9 +0,0 @@
#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,4 +1,3 @@
#include "localserver.h"
#include "mainwindow.h" #include "mainwindow.h"
#include "virtmic.h" #include "virtmic.h"
@ -8,19 +7,15 @@
#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-awesomeaudio");
QApplication::setWindowIcon( QApplication::setWindowIcon(
QIcon(":assets/de.shorsh.discord-screenaudio.png")); QIcon(":assets/de.shorsh.discord-screenaudio.png"));
QApplication::setApplicationVersion(DISCORD_SCEENAUDIO_VERSION_FULL); QApplication::setApplicationVersion(DISCORD_SCEENAUDIO_VERSION_FULL);
QApplication::setDesktopFileName("lol.deadzone.discord-awesomeaudio"); QApplication::setDesktopFileName("de.shorsh.discord-screenaudio");
qSetMessagePattern("[%{category}] %{message}"); qSetMessagePattern("[%{category}] %{message}");
@ -29,9 +24,6 @@ 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);
@ -41,10 +33,6 @@ 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"));
@ -55,20 +43,6 @@ 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-awesomeaudio";
if (isProgramRunning(program_name)) {
// if running show error message
showErrorMessage("discord-awesomeaudio 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();

View File

@ -12,154 +12,121 @@
#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>
#include <QUrl> #include <QUrl>
#include <QWebEngineFullScreenRequest> #include <QWebEngineFullScreenRequest>
#include <QWebEngineProfile>
#include <QWebEngineUrlRequestInterceptor>
#include <QWidget> #include <QWidget>
// Custom network interceptor class
class NetworkInterceptor : public QWebEngineUrlRequestInterceptor {
public:
NetworkInterceptor(QObject* parent = nullptr) : QWebEngineUrlRequestInterceptor(parent) {}
void interceptRequest(QWebEngineUrlRequestInfo& info) override {
// Modify the request headers here as needed
info.setHttpHeader("Access-Control-Allow-Origin", "*");
info.setHttpHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
}
};
MainWindow *MainWindow::m_instance = nullptr; MainWindow *MainWindow::m_instance = nullptr;
MainWindow::MainWindow(bool useNotifySend, QWidget *parent) MainWindow::MainWindow(bool useNotifySend, QWidget *parent)
: QMainWindow(parent) { : QMainWindow(parent) {
assert(MainWindow::m_instance == nullptr); assert(MainWindow::m_instance == nullptr);
MainWindow::m_instance = this; MainWindow::m_instance = this;
setupSettings(); setupSettings();
m_settings->setValue("useNotifySend", useNotifySend); m_settings->setValue("useNotifySend", useNotifySend);
m_centralWidget = new CentralWidget(this); m_centralWidget = new CentralWidget(this);
setCentralWidget(m_centralWidget); setCentralWidget(m_centralWidget);
setupTrayIcon();
// Create and install the network interceptor if (m_settings->contains("geometry")) {
NetworkInterceptor* networkInterceptor = new NetworkInterceptor(this); restoreGeometry(m_settings->value("geometry").toByteArray());
QWebEngineProfile::defaultProfile()->setRequestInterceptor(networkInterceptor); } else {
resize(1000, 700);
setupTrayIcon(); showMaximized();
setMinimumSize(800, 300); }
connect(new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_Q), this), if (m_settings->value("trayIcon", false).toBool() &&
&QShortcut::activated, this, &MainWindow::toggleOrCloseWindow); m_settings->value("startHidden", false).toBool()) {
if (m_settings->contains("geometry")) { hide();
restoreGeometry(m_settings->value("geometry").toByteArray()); QTimer::singleShot(0, [=]() { hide(); });
} else { }
resize(1000, 700);
showMaximized();
}
if (m_settings->value("trayIcon", false).toBool() &&
m_settings->value("startHidden", false).toBool()) {
hide();
QTimer::singleShot(0, [=]() { hide(); });
}
} }
void MainWindow::fullScreenRequested( void MainWindow::fullScreenRequested(
QWebEngineFullScreenRequest fullScreenRequest) { QWebEngineFullScreenRequest fullScreenRequest) {
fullScreenRequest.accept(); fullScreenRequest.accept();
if (fullScreenRequest.toggleOn()) { if (fullScreenRequest.toggleOn()) {
m_wasMaximized = isMaximized(); m_wasMaximized = isMaximized();
if (!m_wasMaximized) { showFullScreen();
showNormal(); } else {
} m_wasMaximized ? showMaximized() : showNormal();
showFullScreen(); }
} else {
m_wasMaximized ? showMaximized() : showNormal();
}
} }
void MainWindow::setupTrayIcon() { void MainWindow::setupTrayIcon() {
if (m_settings->value("trayIcon", false).toBool() == false || if (m_settings->value("trayIcon", false).toBool() == false ||
m_trayIcon != nullptr) m_trayIcon != nullptr)
return; return;
auto aboutAction = new QAction( auto aboutAction = new QAction(
"discord-awesomeaudio v" + QString(DISCORD_SCEENAUDIO_VERSION_FULL), this); "discord-screenaudio v" + QString(DISCORD_SCEENAUDIO_VERSION_FULL), this);
aboutAction->setIcon(QIcon(":assets/de.shorsh.discord-screenaudio.png")); aboutAction->setIcon(QIcon(":assets/de.shorsh.discord-screenaudio.png"));
aboutAction->setEnabled(false); aboutAction->setEnabled(false);
auto exitAction = new QAction("Exit", this); auto exitAction = new QAction("Exit", this);
connect(exitAction, &QAction::triggered, []() { QApplication::quit(); }); connect(exitAction, &QAction::triggered, []() { QApplication::quit(); });
m_trayIconMenu = new QMenu(this); m_trayIconMenu = new QMenu(this);
m_trayIconMenu->addAction(aboutAction); m_trayIconMenu->addAction(aboutAction);
m_trayIconMenu->addAction(exitAction); m_trayIconMenu->addAction(exitAction);
m_trayIcon = new QSystemTrayIcon(this); m_trayIcon = new QSystemTrayIcon(this);
m_trayIcon->setContextMenu(m_trayIconMenu); m_trayIcon->setContextMenu(m_trayIconMenu);
m_trayIcon->setIcon(QIcon(":assets/de.shorsh.discord-screenaudio.png")); m_trayIcon->setIcon(QIcon(":assets/de.shorsh.discord-screenaudio.png"));
m_trayIcon->show(); m_trayIcon->show();
connect(m_trayIcon, &QSystemTrayIcon::activated, [this](auto reason) { connect(m_trayIcon, &QSystemTrayIcon::activated, [this](auto reason) {
if (reason == QSystemTrayIcon::Trigger) { if (reason == QSystemTrayIcon::Trigger) {
toggleOrCloseWindow(); if (isVisible()) {
} hide();
}); } else {
show();
activateWindow();
}
}
});
} }
void MainWindow::cleanTrayIcon() { void MainWindow::cleanTrayIcon() {
if (m_trayIcon == nullptr) if (m_trayIcon == nullptr)
return; return;
m_trayIcon->hide(); m_trayIcon->hide();
m_trayIconMenu->deleteLater(); m_trayIconMenu->deleteLater();
m_trayIcon->deleteLater(); m_trayIcon->deleteLater();
m_trayIconMenu = nullptr; m_trayIconMenu = nullptr;
m_trayIcon = nullptr; m_trayIcon = nullptr;
} }
void MainWindow::setupSettings() { void MainWindow::setupSettings() {
m_settings = m_settings =
new QSettings("discord-awesomeaudio", "discord-awesomeaudio", this); new QSettings("discord-screenaudio", "discord-screenaudio", this);
m_settings->beginGroup("settings"); m_settings->beginGroup("settings");
m_settings->endGroup(); m_settings->endGroup();
} }
QSettings *MainWindow::settings() const { return m_settings; } QSettings *MainWindow::settings() const { return m_settings; }
void MainWindow::setTrayIcon(bool enabled) { void MainWindow::setTrayIcon(bool enabled) {
m_settings->setValue("trayIcon", enabled); m_settings->setValue("trayIcon", enabled);
if (enabled) { if (enabled) {
setupTrayIcon(); setupTrayIcon();
} else { } else {
cleanTrayIcon(); cleanTrayIcon();
} }
} }
void MainWindow::closeEvent(QCloseEvent *event) { void MainWindow::closeEvent(QCloseEvent *event) {
if (m_settings->value("trayIcon", false).toBool()) { if (m_settings->value("trayIcon", false).toBool()) {
hide(); hide();
} else { } else {
m_settings->setValue("geometry", saveGeometry()); m_settings->setValue("geometry", saveGeometry());
QApplication::quit(); QApplication::quit();
} }
} }
MainWindow *MainWindow::instance() { return m_instance; } 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();
}
}

View File

@ -1,6 +1,7 @@
#pragma once #pragma once
#include "centralwidget.h" #include "centralwidget.h"
#include "virtmic.h"
#include <QMainWindow> #include <QMainWindow>
#include <QMenu> #include <QMenu>
@ -10,31 +11,30 @@
#include <QSystemTrayIcon> #include <QSystemTrayIcon>
#include <QVBoxLayout> #include <QVBoxLayout>
#include <QVector> #include <QVector>
#include <QWebEngineUrlRequestInterceptor>
class MainWindow : public QMainWindow { class MainWindow : public QMainWindow {
Q_OBJECT Q_OBJECT
public: public:
explicit MainWindow(bool useNotifySend = false, QWidget *parent = nullptr); explicit MainWindow(bool useNotifySend = false, QWidget *parent = nullptr);
static MainWindow *instance(); static MainWindow *instance();
QSettings *settings() const; QSettings *settings() const;
static CentralWidget *centralWidget(); static CentralWidget *centralWidget();
private: private:
void setupTrayIcon(); void setupTrayIcon();
void cleanTrayIcon(); void cleanTrayIcon();
void setupSettings(); void setupSettings();
void closeEvent(QCloseEvent *event) override; QWebEngineProfile *prepareProfile();
QSystemTrayIcon *m_trayIcon = nullptr; void closeEvent(QCloseEvent *event) override;
QMenu *m_trayIconMenu; QSystemTrayIcon *m_trayIcon = nullptr;
QSettings *m_settings; QMenu *m_trayIconMenu;
bool m_wasMaximized; QSettings *m_settings;
static MainWindow *m_instance; bool m_wasMaximized;
CentralWidget *m_centralWidget; static MainWindow *m_instance;
CentralWidget *m_centralWidget;
public Q_SLOTS: public Q_SLOTS:
void setTrayIcon(bool enabled); void setTrayIcon(bool enabled);
void fullScreenRequested(QWebEngineFullScreenRequest fullScreenRequest); void fullScreenRequested(QWebEngineFullScreenRequest fullScreenRequest);
void toggleOrCloseWindow();
}; };

View File

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

View File

@ -21,7 +21,6 @@ UserScript::UserScript() : QObject() {
setupVirtmic(); setupVirtmic();
} }
void UserScript::setupHelpMenu() { void UserScript::setupHelpMenu() {
#ifdef KXMLGUI #ifdef KXMLGUI
m_kxmlgui = true; m_kxmlgui = true;
@ -43,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.3", aboutData.addComponent("Rohrkabel", "A C++ RAII Pipewire-API Wrapper", "1.5",
"https://github.com/Soundux/rohrkabel"); "https://github.com/Soundux/rohrkabel");
aboutData.addComponent("arRPC", aboutData.addComponent(
"An open implementation of Discord's local RPC " "Soundux/channel ",
"servers<br>Copyright (c) 2022 OpenAsar", "A C++ implementation of Rust's std::sync::mpsc::channel", nullptr,
nullptr, "https://github.com/OpenAsar/arrpc"); "https://github.com/Soundux/channel");
m_helpMenu = new KHelpMenu(MainWindow::instance(), aboutData); m_helpMenu = new KHelpMenu(MainWindow::instance(), aboutData);
#endif #endif
} }
@ -84,7 +83,8 @@ void UserScript::setupShortcutsDialog() {
void UserScript::setupStreamDialog() { void UserScript::setupStreamDialog() {
m_streamDialog = new StreamDialog(MainWindow::instance()); m_streamDialog = new StreamDialog(MainWindow::instance());
connect(m_streamDialog, &StreamDialog::requestedStreamStart, this, &UserScript::startStream); connect(m_streamDialog, &StreamDialog::requestedStreamStart, this,
&UserScript::startStream);
} }
void UserScript::setupVirtmic() { void UserScript::setupVirtmic() {
@ -155,13 +155,13 @@ void UserScript::startVirtmic(QString target) {
m_virtmicProcess.start(QApplication::arguments()[0], {"--virtmic", target}); m_virtmicProcess.start(QApplication::arguments()[0], {"--virtmic", target});
} }
void UserScript::startStream(bool video, bool audio, int width, int height, int frameRate, QString target) { void UserScript::startStream(bool video, bool audio, int width, int height,
int frameRate, QString target) {
stopVirtmic(); stopVirtmic();
startVirtmic(audio ? target : "[None]"); startVirtmic(audio ? target : "[None]");
// Wait a bit for the virtmic to start // Wait a bit for the virtmic to start
QTimer::singleShot(200, [=]() { QTimer::singleShot(
emit streamStarted(video, width, height, frameRate); 200, [=]() { emit streamStarted(video, width, height, frameRate); });
});
} }
void UserScript::showStreamDialog() { void UserScript::showStreamDialog() {
@ -173,7 +173,8 @@ void UserScript::showStreamDialog() {
} }
void UserScript::showThemeDialog() { void UserScript::showThemeDialog() {
auto url = QInputDialog::getText(MainWindow::instance(), "Theme Installation", "Please enter the URL of the Theme"); auto url = QInputDialog::getText(MainWindow::instance(), "Theme Installation",
"Please enter the URL of the Theme");
if (url != "") if (url != "")
emit shouldInstallUserStyles(url); emit shouldInstallUserStyles(url);
} }
@ -181,68 +182,3 @@ void UserScript::showThemeDialog() {
void UserScript::installUserStyles(QString url) { void UserScript::installUserStyles(QString url) {
emit shouldInstallUserStyles(url); emit shouldInstallUserStyles(url);
} }
QVariant UserScript::vencordSend(QString event, QVariantList args) {
QString configFolder =
QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation) +
"/vencord";
QString quickCssFile = configFolder + "/quickCss.css";
QString settingsFile = configFolder + "/settings.json";
if (!QDir().exists(configFolder))
QDir().mkpath(configFolder);
if (event == "VencordGetRepo") {
return true;
}
if (event == "VencordGetSettingsDir") {
return configFolder;
}
if (event == "VencordGetQuickCss") {
if (QFile::exists(quickCssFile)) {
QFile file(quickCssFile);
if (!file.open(QIODevice::ReadOnly))
qFatal("Failed to load %s with error: %s",
quickCssFile.toLatin1().constData(),
file.errorString().toLatin1().constData());
auto content = file.readAll();
file.close();
return QString(content);
} else
return "";
}
if (event == "VencordGetSettings") {
if (QFile::exists(settingsFile)) {
QFile file(settingsFile);
if (!file.open(QIODevice::ReadOnly))
qFatal("Failed to load %s with error: %s",
settingsFile.toLatin1().constData(),
file.errorString().toLatin1().constData());
auto content = file.readAll();
file.close();
return QString(content);
} else
return "{}";
}
if (event == "VencordSetSettings") {
QFile file(settingsFile);
if (!file.open(QIODevice::WriteOnly))
qFatal("Failed to load %s with error: %s",
settingsFile.toLatin1().constData(),
file.errorString().toLatin1().constData());
file.write(args[0].toString().toUtf8());
file.close();
return true;
}
if (event == "VencordGetUpdates") {
return QVariantMap{{"ok", true}, {"value", QVariantList()}};
}
if (event == "VencordOpenExternal") {
QDesktopServices::openUrl(QUrl(args[0].toString()));
return true;
}
if (event == "VencordOpenQuickCss") {
return true;
}
assert(false);
}

View File

@ -1,10 +1,9 @@
#pragma once #pragma once
#include "streamdialog.h" #include "streamdialog.h"
#include <QProcess>
#include <QObject> #include <QObject>
#include <QDir> #include <QProcess>
#include <QDesktopServices>
#ifdef KXMLGUI #ifdef KXMLGUI
#include <KAboutData> #include <KAboutData>
@ -24,66 +23,61 @@
#endif #endif
class UserScript : public QObject { class UserScript : public QObject {
Q_OBJECT Q_OBJECT
public: public:
UserScript(); UserScript();
bool isVirtmicRunning(); bool isVirtmicRunning();
Q_PROPERTY(QString version READ version CONSTANT) Q_PROPERTY(QString version READ version CONSTANT);
Q_PROPERTY(bool kxmlgui MEMBER m_kxmlgui CONSTANT) Q_PROPERTY(bool kxmlgui MEMBER m_kxmlgui CONSTANT);
Q_PROPERTY(bool kglobalaccel MEMBER m_kglobalaccel CONSTANT) Q_PROPERTY(bool kglobalaccel MEMBER m_kglobalaccel CONSTANT);
Q_PROPERTY(QString userstyles MEMBER m_userstyles NOTIFY userstylesChanged) Q_PROPERTY(QString userstyles MEMBER m_userstyles NOTIFY userstylesChanged);
Q_PROPERTY(QString loadingMessage MEMBER m_loadingMessage NOTIFY loadingMessageChanged) Q_PROPERTY(QString loadingMessage MEMBER m_loadingMessage NOTIFY
loadingMessageChanged);
private: private:
QProcess m_virtmicProcess; QProcess m_virtmicProcess;
StreamDialog *m_streamDialog; StreamDialog *m_streamDialog;
bool m_kxmlgui = false; bool m_kxmlgui = false;
bool m_kglobalaccel = false; bool m_kglobalaccel = false;
QString m_userstyles; QString m_userstyles;
QString m_loadingMessage; QString m_loadingMessage;
QString m_vencordSettings;
void setupHelpMenu();
void setupShortcutsDialog();
void setupStreamDialog();
void setupVirtmic();
#ifdef KXMLGUI #ifdef KXMLGUI
KHelpMenu *m_helpMenu; KHelpMenu *m_helpMenu;
#ifdef KGLOBALACCEL #ifdef KGLOBALACCEL
KActionCollection *m_actionCollection; KActionCollection *m_actionCollection;
KShortcutsDialog *m_shortcutsDialog; KShortcutsDialog *m_shortcutsDialog;
#endif #endif
#endif #endif
void setupHelpMenu();
void setupShortcutsDialog();
void setupStreamDialog();
void setupVirtmic();
Q_SIGNALS: Q_SIGNALS:
void muteToggled(); void muteToggled();
void deafenToggled(); void deafenToggled();
void streamStarted(bool video, int width, int height, int frameRate); void streamStarted(bool video, int width, int height, int frameRate);
void userstylesChanged(); void userstylesChanged();
void loadingMessageChanged(QString message); void loadingMessageChanged(QString message);
void shouldInstallUserStyles(QString url); void shouldInstallUserStyles(QString url);
public Q_SLOTS: public Q_SLOTS:
void log(QString message); void log(QString message);
QString version(); QString version();
QVariant getPref(QString name, QVariant fallback); QVariant getPref(QString name, QVariant fallback);
bool getBoolPref(QString name, bool fallback); bool getBoolPref(QString name, bool fallback);
void setPref(QString name, QVariant value); void setPref(QString name, QVariant value);
void setTrayIcon(bool value); void setTrayIcon(bool value);
void showShortcutsDialog(); void showShortcutsDialog();
void showHelpMenu(); void showHelpMenu();
void showStreamDialog(); void showStreamDialog();
void stopVirtmic(); void stopVirtmic();
void startVirtmic(QString target); void startVirtmic(QString target);
void showThemeDialog(); void showThemeDialog();
void installUserStyles(QString url); void installUserStyles(QString url);
QVariant vencordSend(QString event, QVariantList args);
private Q_SLOTS: private Q_SLOTS:
void startStream(bool video, bool audio, int width, int height, int frameRate, QString target); void startStream(bool video, bool audio, int width, int height, int frameRate,
QString target);
}; };

View File

@ -1,200 +1,174 @@
#include "virtmic.h" #include "virtmic.h"
#include "log.h" #include "log.h"
#include <rohrkabel/loop/main.hpp> QThread virtmicThread;
#include <rohrkabel/registry/registry.hpp> std::unique_ptr<pipewire::sender<Virtmic::set_target, Virtmic::terminate>>
senderr;
std::unique_ptr<cr::receiver<Virtmic::new_targets>> receiverr;
namespace Virtmic { void Virtmic::instance() {
if (!virtmicThread.isRunning()) {
const QStringList EXCLUDE_TARGETS{"Chromium input", "discord-awesomeaudio", "pavucontrol", "PulseAudio Volume Control", "OBS", "OBS-Monitor", "OBS/OBS-MONITOR"}; auto [main_sender, main_receiver] = cr::channel<new_targets>();
auto [pw_sender, pw_receiver] = pipewire::channel<set_target, terminate>();
const std::string nullstr = ""; Virtmic *virtmic =
const std::string &getTarget(const pipewire::spa::dict &props) { new Virtmic(std::move(pw_receiver), std::move(main_sender));
if (props.count("media.class") && virtmic->moveToThread(&virtmicThread);
props.at("media.class") == "Stream/Output/Audio") { virtmicThread.start();
if (props.count("application.name") && props.at("application.name") != "") QMetaObject::invokeMethod(virtmic, "run");
return props.at("application.name"); receiverr = std::make_unique<cr::receiver<Virtmic::new_targets>>(
else if (props.count("application.process.binary") && std::move(main_receiver));
props.at("application.process.binary") != "") senderr = std::make_unique<
return props.at("application.process.binary"); pipewire::sender<Virtmic::set_target, Virtmic::terminate>>(
else std::move(pw_sender));
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 (EXCLUDE_TARGETS.contains(QString::fromStdString(name)))
continue;
if (parent.props.count("media.class") && (parent.props.at("media.class") == "Audio/Source" || parent.props.at("media.class") == "Audio/Source/Virtual"))
continue;
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-awesomeaudio-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>(); void Virtmic::setTarget(QString target) {
reg_events.on<pipewire::registry_event::global>( senderr.get()->send<Virtmic::set_target>({target});
[&](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();
if (!nodes.count(global.id)) { void Virtmic::getTargets() { senderr.get()->send<Virtmic::get_targets>(); }
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")) { Virtmic::Virtmic(pipewire::receiver<set_target, terminate> receiver,
auto node_id = std::stoul(info.props["node.id"]); cr::sender<new_targets> sender) {
m_receiver = std::make_unique<pipewire::receiver<set_target, terminate>>(
if (node_id == virtual_mic.id() && std::move(receiver));
info.direction == pipewire::port_direction::input) { m_sender = std::make_unique<cr::sender<new_targets>>(std::move(sender));
if (info.props["audio.channel"] == "FL") { virtual_mic = std::make_unique<pipewire::proxy>(
virt_fl = std::make_unique<pipewire::port>(std::move(port)); std::move(*core.create("adapter",
} else { {{"node.name", "discord-screenaudio-virtmic"},
virt_fr = std::make_unique<pipewire::port>(std::move(port)); {"media.class", "Audio/Source/Virtual"},
} {"factory.name", "support.null-audio-sink"},
} else { {"audio.channels", "2"},
ports.emplace(global.id, std::move(port)); {"audio.position", "FL,FR"}},
} pipewire::node::type, pipewire::node::version,
pipewire::update_strategy::none)
link(target, core); .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); });
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();
} }
} }
} // 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);
}
}

View File

@ -1,12 +1,63 @@
#pragma once #pragma once
#include <QString> #include <cr/channel.hpp>
#include <QVector>
#include <iostream> #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(); class Virtmic : public QObject {
void start(QString _target); 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 +0,0 @@
Subproject commit 62b2acebe6806c7b0e2ca6a43c6b2419a627b8dc

1
submodules/channel Submodule

@ -0,0 +1 @@
Subproject commit 6977815409b4c3c02d74a7aee3fc29f01d632feb

@ -1 +1 @@
Subproject commit 04bfb921c44fb0d2337df70f5660899bc8d2844f Subproject commit 8a7705be070190a88b9a9d3619fa2fb7eabc951e

View File

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

View File

@ -1,3 +0,0 @@
git pull
./uninstall.sh
./install.sh