Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d184823ee9 | ||
|
|
dd2beed4eb | ||
|
|
3740553aba | ||
|
|
555f5c62f5 | ||
|
|
af3c62c263 | ||
|
|
c4f6c33f9d | ||
|
|
758ffa8cb5 | ||
|
|
753da8b1d3 | ||
|
|
5f37ab314f | ||
|
|
c7bff3f428 | ||
|
|
34e23f7187 | ||
|
|
c325dd6798 | ||
|
|
b42fde85d1 | ||
|
|
cc379ada85 | ||
|
|
fdb6145bbd | ||
|
|
8bd4d1896a | ||
|
|
1a2d09fe3d | ||
|
|
f01c941c90 | ||
|
|
95d391e869 | ||
|
|
af107168db | ||
|
|
e7e6c5d82b | ||
|
|
ee0c66762d | ||
|
|
c3483a42db | ||
|
|
d68797cebb | ||
|
|
ef221d4574 | ||
|
|
3fa2e3fa3c |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1 +1,2 @@
|
||||
/build
|
||||
.vscode
|
||||
|
||||
@@ -18,12 +18,28 @@ find_package(Qt5 CONFIG REQUIRED COMPONENTS
|
||||
WebEngineWidgets
|
||||
)
|
||||
|
||||
find_package(KF5Notifications)
|
||||
if(KF5Notifications_FOUND)
|
||||
add_definitions( -DKNOTIFICATIONS )
|
||||
endif()
|
||||
|
||||
find_package(KF5XmlGui)
|
||||
if(KF5XmlGui_FOUND)
|
||||
add_definitions( -DKXMLGUI )
|
||||
endif()
|
||||
|
||||
find_package(KF5GlobalAccel)
|
||||
if(KF5GlobalAccel_FOUND)
|
||||
add_definitions( -DKGLOBALACCEL )
|
||||
endif()
|
||||
|
||||
set(discord-screenaudio_SRC
|
||||
src/main.cpp
|
||||
src/mainwindow.cpp
|
||||
src/virtmic.cpp
|
||||
src/discordpage.cpp
|
||||
src/streamdialog.cpp
|
||||
src/log.cpp
|
||||
resources.qrc
|
||||
)
|
||||
|
||||
@@ -52,6 +68,17 @@ add_executable(discord-screenaudio ${discord-screenaudio_SRC})
|
||||
|
||||
target_link_libraries(discord-screenaudio Qt5::Widgets Qt5::WebEngineWidgets rohrkabel)
|
||||
|
||||
if(KF5Notifications_FOUND)
|
||||
target_link_libraries(discord-screenaudio KF5::Notifications)
|
||||
install(FILES assets/discord-screenaudio.notifyrc DESTINATION ${CMAKE_INSTALL_PREFIX}/share/knotifications5)
|
||||
endif()
|
||||
if(KF5XmlGui_FOUND)
|
||||
target_link_libraries(discord-screenaudio KF5::XmlGui)
|
||||
endif()
|
||||
if(KF5GlobalAccel_FOUND)
|
||||
target_link_libraries(discord-screenaudio KF5::GlobalAccel)
|
||||
endif()
|
||||
|
||||
install(TARGETS discord-screenaudio DESTINATION bin)
|
||||
install(FILES assets/de.shorsh.discord-screenaudio.png DESTINATION ${CMAKE_INSTALL_PREFIX}/share/icons/hicolor/256x256/apps)
|
||||
install(PROGRAMS assets/de.shorsh.discord-screenaudio.desktop DESTINATION ${CMAKE_INSTALL_PREFIX}/share/applications)
|
||||
|
||||
37
README.md
37
README.md
@@ -1,13 +1,17 @@
|
||||
# discord-screenaudio
|
||||
|
||||
A very WIP custom discord client that supports streaming with audio on Linux,
|
||||
made possible by the
|
||||
A custom discord client that supports streaming with audio on Linux, made
|
||||
possible by the
|
||||
[great work](https://github.com/edisionnano/Screenshare-with-audio-on-Discord-with-Linux)
|
||||
of [@edisionnano](https://github.com/edisionnano) and the
|
||||
[Rohrkabel library](https://github.com/Soundux/rohrkabel) by
|
||||
[@Curve](https://github.com/Curve).
|
||||
|
||||

|
||||
Unlike a lot of other solutions, the audio here is directly fed into the
|
||||
screenshare and not passed to the user microphone
|
||||
([see explanation](#how-it-works)).
|
||||
|
||||

|
||||
|
||||
The purpose of this project is **not** to provide an alternative to the original
|
||||
Discord client. Rather, it should be used in addition to the original client in
|
||||
@@ -15,18 +19,31 @@ case you want to stream something, maybe used with a second account. For
|
||||
anything else, this client has way too many things that work less well than in
|
||||
the original client.
|
||||
|
||||
Technically this could be against
|
||||
[Discord's TOS](https://discord.com/terms#software-in-discord%E2%80%99s-services),
|
||||
so be warned. Discord probably won't ban you for using this, but if they do I
|
||||
told you and it's not my fault.
|
||||
|
||||
## Known Issues
|
||||
|
||||
- Only works with **PipeWire**
|
||||
- Only works on **X11**
|
||||
- Can only share primary screen (no other screens or specific applications) (see
|
||||
- Can only share primary screen on X11 (no other screens or specific
|
||||
applications) (see
|
||||
[#1](https://github.com/maltejur/discord-screenaudio/issues/1))
|
||||
|
||||
## Installation
|
||||
|
||||
If you are using Arch, you can build and install
|
||||
[from the AUR](https://aur.archlinux.org/packages/discord-screenaudio). If you
|
||||
aren't, you will have to [build it yourself](#building-from-source).
|
||||
You have multiple options:
|
||||
|
||||
- Install the Flatpak (recommended):
|
||||
`flatpak install de.shorsh.discord-screenaudio`
|
||||
- If you are on Arch, you can build and install
|
||||
[`discord-screenaudio`](https://aur.archlinux.org/packages/discord-screenaudio)
|
||||
from the AUR
|
||||
- If you are on openSUSE, you can use the
|
||||
[Open Build Service package](https://software.opensuse.org//download.html?project=games%3Atools&package=discord-screenaudio)
|
||||
by [@VortexAcherontic](https://github.com/VortexAcherontic)
|
||||
- You can [build it yourself](#building-from-source)
|
||||
|
||||
## Building from Source
|
||||
|
||||
@@ -37,9 +54,11 @@ aren't, you will have to [build it yourself](#building-from-source).
|
||||
- Qt5 and QtWebEngine
|
||||
- **PipeWire** (it currently doesn't work with PulseAudio)
|
||||
- Git
|
||||
- _Kf5Notifications (optional, for better notifications)_
|
||||
- _KXMLGui and KGlobalAccel (optional, for keybinds)_
|
||||
|
||||
On Debian:
|
||||
`apt install -y build-essential cmake qtbase5-dev qtwebengine5-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`
|
||||
|
||||
### Building
|
||||
|
||||
|
||||
@@ -2,5 +2,7 @@
|
||||
Type=Application
|
||||
Name=discord-screenaudio
|
||||
Exec=discord-screenaudio
|
||||
Comment=A custom discord client that supports streaming with audio on Linux.
|
||||
Icon=de.shorsh.discord-screenaudio
|
||||
Categories=Network;InstantMessaging;
|
||||
Terminal=false
|
||||
|
||||
@@ -8,11 +8,18 @@
|
||||
<release version="${DISCORD_SCEENAUDIO_VERSION_FULL}" timestamp="${TIMESTAMP}" />
|
||||
</releases>
|
||||
|
||||
<summary>A very WIP custom discord client that supports streaming with audio on Linux</summary>
|
||||
<summary>A custom discord client that supports streaming with audio on Linux</summary>
|
||||
|
||||
<description>
|
||||
<p>A very WIP custom discord client that supports streaming with audio on Linux, made possible by the great work of @edisionnano and the Rohrkabel library by @Curve.</p>
|
||||
<p>A custom discord client that supports streaming with audio on Linux, made possible by the great work of @edisionnano and the Rohrkabel library by @Curve.</p>
|
||||
<p>Unlike a lot of other solutions, the audio here is directly fed into the screenshare and not passed to the user microphone.</p>
|
||||
<p>The purpose of this project is not to provide an alternative to the original Discord client. Rather, it should be used in addition to the original client in case you want to stream something, maybe used with a second account. For anything else, this client has way too many things that work less well than in the original client.</p>
|
||||
<p>Technically this could be against Discord's TOS, so be warned. Discord probably won't ban you for using this, but if they do I told you and it's not my fault.</p>
|
||||
<p>Known issues:</p>
|
||||
<ul>
|
||||
<li>Only works with PipeWire</li>
|
||||
<li>Can only share primary screen on X11</li>
|
||||
</ul>
|
||||
</description>
|
||||
|
||||
<launchable type="desktop-id">
|
||||
@@ -22,7 +29,7 @@
|
||||
<screenshots>
|
||||
<screenshot type="default">
|
||||
<image>
|
||||
https://user-images.githubusercontent.com/48161361/179571245-11ea05f3-fb5e-4aef-9132-2736e122ef04.png
|
||||
https://user-images.githubusercontent.com/48161361/192137080-33466cf7-8c56-4373-90c6-01ea74b6fb83.png
|
||||
</image>
|
||||
</screenshot>
|
||||
</screenshots>
|
||||
|
||||
8
assets/discord-screenaudio.notifyrc
Normal file
8
assets/discord-screenaudio.notifyrc
Normal file
@@ -0,0 +1,8 @@
|
||||
[Global]
|
||||
IconName=de.shorsh.discord-screenaudio
|
||||
Name=discord-screenaudio
|
||||
DesktopEntry=de.shorsh.discord-screenaudio.desktop
|
||||
|
||||
[Event/discordNotification]
|
||||
Name=Discord Notification
|
||||
Action=Popup
|
||||
@@ -3,12 +3,25 @@
|
||||
navigator.mediaDevices.chromiumGetDisplayMedia =
|
||||
navigator.mediaDevices.getDisplayMedia;
|
||||
|
||||
function sleep(ms) {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
const getAudioDevice = async (nameOfAudioDevice) => {
|
||||
await navigator.mediaDevices.getUserMedia({
|
||||
audio: true,
|
||||
});
|
||||
let devices = await navigator.mediaDevices.enumerateDevices();
|
||||
let audioDevice = devices.find(({ label }) => label === nameOfAudioDevice);
|
||||
let audioDevice;
|
||||
while (audioDevice === undefined) {
|
||||
let devices = await navigator.mediaDevices.enumerateDevices();
|
||||
audioDevice = devices.find(({ label }) => label === nameOfAudioDevice);
|
||||
if (!audioDevice)
|
||||
console.log(
|
||||
`dsa: Did not find '${nameOfAudioDevice}', trying again in 100ms`
|
||||
);
|
||||
await sleep(100);
|
||||
}
|
||||
console.log(`dsa: Found '${nameOfAudioDevice}'`);
|
||||
return audioDevice;
|
||||
};
|
||||
|
||||
@@ -99,6 +112,7 @@ setInterval(() => {
|
||||
const initialDisplay = el.style.display;
|
||||
|
||||
window.discordScreenaudioStartStream = (width, height, frameRate) => {
|
||||
window.discordScreenaudioResolutionString = `${height}p ${frameRate}FPS`;
|
||||
setGetDisplayMedia({
|
||||
audio: true,
|
||||
video: { width, height, frameRate },
|
||||
@@ -121,12 +135,21 @@ setInterval(() => {
|
||||
document.getElementsByClassName("dirscordScreenaudioAboutText").length == 0
|
||||
) {
|
||||
for (const el of document.getElementsByClassName("info-3pQQBb")) {
|
||||
const aboutEl = document.createElement("div");
|
||||
let aboutEl;
|
||||
if (window.discordScreenaudioKXMLGUI) {
|
||||
aboutEl = document.createElement("a");
|
||||
aboutEl.addEventListener("click", () => {
|
||||
console.log("!discord-screenaudio-about");
|
||||
});
|
||||
} else {
|
||||
aboutEl = document.createElement("div");
|
||||
}
|
||||
aboutEl.innerText = `discord-screenaudio ${window.discordScreenaudioVersion}`;
|
||||
aboutEl.style.fontSize = "12px";
|
||||
aboutEl.style.color = "var(--text-muted)";
|
||||
aboutEl.style.textTransform = "none";
|
||||
aboutEl.classList.add("dirscordScreenaudioAboutText");
|
||||
aboutEl.style.cursor = "pointer";
|
||||
el.appendChild(aboutEl);
|
||||
}
|
||||
}
|
||||
@@ -134,6 +157,48 @@ setInterval(() => {
|
||||
// Remove stream settings if stream is active
|
||||
document.getElementById("manage-streams-change-windows")?.remove();
|
||||
document.querySelector(`[aria-label="Stream Settings"]`)?.remove();
|
||||
|
||||
// Add event listener for keybind tab
|
||||
if (
|
||||
document
|
||||
.getElementById("keybinds-tab")
|
||||
?.getElementsByClassName(
|
||||
"container-3jbRo5 info-1hMolH fontSize16-3zr6Io browserNotice-1u-Y5o"
|
||||
).length
|
||||
) {
|
||||
const el = document
|
||||
.getElementById("keybinds-tab")
|
||||
.getElementsByClassName("children-1xdcWE")[0];
|
||||
const div = document.createElement("div");
|
||||
div.style.marginBottom = "50px";
|
||||
const button = document.createElement("button");
|
||||
button.classList =
|
||||
"button-f2h6uQ lookFilled-yCfaCM colorBrand-I6CyqQ sizeSmall-wU2dO- grow-2sR_-F";
|
||||
button.innerText = "Edit Global Keybinds";
|
||||
button.addEventListener("click", () => {
|
||||
console.log("!discord-screenaudio-keybinds");
|
||||
});
|
||||
div.appendChild(button);
|
||||
el.innerHTML = "";
|
||||
el.appendChild(div);
|
||||
}
|
||||
|
||||
const muteBtn = document.getElementsByClassName(
|
||||
"button-12Fmur enabled-9OeuTA button-f2h6uQ lookBlank-21BCro colorBrand-I6CyqQ grow-2sR_-F"
|
||||
)[0];
|
||||
window.discordScreenaudioToggleMute = () => muteBtn.click();
|
||||
const deafenBtn = document.getElementsByClassName(
|
||||
"button-12Fmur enabled-9OeuTA button-f2h6uQ lookBlank-21BCro colorBrand-I6CyqQ grow-2sR_-F"
|
||||
)[1];
|
||||
window.discordScreenaudioToggleDeafen = () => deafenBtn.click();
|
||||
|
||||
if (window.discordScreenaudioResolutionString) {
|
||||
for (const el of document.getElementsByClassName(
|
||||
"qualityIndicator-39wQDy"
|
||||
)) {
|
||||
el.innerHTML = window.discordScreenaudioResolutionString;
|
||||
}
|
||||
}
|
||||
}, 500);
|
||||
|
||||
// Fix for broken discord notifications after restart
|
||||
|
||||
@@ -1,9 +1,25 @@
|
||||
#include "discordpage.h"
|
||||
#include "log.h"
|
||||
#include "mainwindow.h"
|
||||
#include "virtmic.h"
|
||||
|
||||
#ifdef KXMLGUI
|
||||
#include <KAboutData>
|
||||
#include <KHelpMenu>
|
||||
#include <KShortcutsDialog>
|
||||
#include <KXmlGuiWindow>
|
||||
#include <QAction>
|
||||
|
||||
#ifdef KGLOBALACCEL
|
||||
#include <KGlobalAccel>
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
#include <QApplication>
|
||||
#include <QDesktopServices>
|
||||
#include <QFile>
|
||||
#include <QMessageBox>
|
||||
#include <QTimer>
|
||||
#include <QWebChannel>
|
||||
#include <QWebEngineScript>
|
||||
@@ -32,19 +48,86 @@ DiscordPage::DiscordPage(QWidget *parent) : QWebEnginePage(parent) {
|
||||
settings()->setAttribute(QWebEngineSettings::PlaybackRequiresUserGesture,
|
||||
false);
|
||||
settings()->setAttribute(QWebEngineSettings::JavascriptCanOpenWindows, false);
|
||||
settings()->setAttribute(QWebEngineSettings::ScrollAnimatorEnabled, true);
|
||||
|
||||
setUrl(QUrl("https://discord.com/app"));
|
||||
|
||||
injectScript(":/assets/userscript.js");
|
||||
injectVersion(QApplication::applicationVersion());
|
||||
injectScriptFile("userscript.js", ":/assets/userscript.js");
|
||||
|
||||
injectScriptText("version.js",
|
||||
QString("window.discordScreenaudioVersion = '%1';")
|
||||
.arg(QApplication::applicationVersion()));
|
||||
|
||||
#ifdef KXMLGUI
|
||||
injectScriptText("xmlgui.js", "window.discordScreenaudioKXMLGUI = true;");
|
||||
|
||||
KAboutData aboutData(
|
||||
"discord-screenaudio", "discord-screenaudio",
|
||||
QApplication::applicationVersion(),
|
||||
"Custom Discord client with the ability to stream audio on Linux",
|
||||
KAboutLicense::GPL_V3, "Copyright 2022 (C) Malte Jürgens");
|
||||
aboutData.setBugAddress("https://github.com/maltejur/discord-screenaudio");
|
||||
aboutData.addAuthor("Malte Jürgens", "Author", "maltejur@dismail.de",
|
||||
"https://github.com/maltejur");
|
||||
aboutData.addCredit("edisionnano",
|
||||
"For creating and documenting the approach for streaming "
|
||||
"audio in Discord used in this project.",
|
||||
QString(),
|
||||
"https://github.com/edisionnano/"
|
||||
"Screenshare-with-audio-on-Discord-with-Linux");
|
||||
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",
|
||||
"https://github.com/Soundux/rohrkabel");
|
||||
m_helpMenu = new KHelpMenu(parent, aboutData);
|
||||
|
||||
#ifdef KGLOBALACCEL
|
||||
injectScriptText("kglobalaccel.js",
|
||||
"window.discordScreenaudioKGLOBALACCEL = true;");
|
||||
|
||||
auto toggleMuteAction = new QAction(this);
|
||||
toggleMuteAction->setText("Toggle Mute");
|
||||
toggleMuteAction->setIcon(QIcon::fromTheme("microphone-sensitivity-muted"));
|
||||
connect(toggleMuteAction, &QAction::triggered, this,
|
||||
&DiscordPage::toggleMute);
|
||||
|
||||
auto toggleDeafenAction = new QAction(this);
|
||||
toggleDeafenAction->setText("Toggle Deafen");
|
||||
toggleDeafenAction->setIcon(QIcon::fromTheme("audio-volume-muted"));
|
||||
connect(toggleDeafenAction, &QAction::triggered, this,
|
||||
&DiscordPage::toggleDeafen);
|
||||
|
||||
m_actionCollection = new KActionCollection(this);
|
||||
m_actionCollection->addAction("toggleMute", toggleMuteAction);
|
||||
KGlobalAccel::setGlobalShortcut(toggleMuteAction, QList<QKeySequence>{});
|
||||
m_actionCollection->addAction("toggleDeafen", toggleDeafenAction);
|
||||
KGlobalAccel::setGlobalShortcut(toggleDeafenAction, QList<QKeySequence>{});
|
||||
|
||||
m_shortcutsDialog = new KShortcutsDialog(KShortcutsEditor::GlobalAction);
|
||||
m_shortcutsDialog->addCollection(m_actionCollection);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
connect(&m_streamDialog, &StreamDialog::requestedStreamStart, this,
|
||||
&DiscordPage::startStream);
|
||||
}
|
||||
|
||||
void DiscordPage::injectScript(QString source) {
|
||||
qDebug() << "[main ] Injecting " << source;
|
||||
void DiscordPage::injectScriptText(QString name, QString content) {
|
||||
qDebug(mainLog) << "Injecting " << name;
|
||||
|
||||
QWebEngineScript script;
|
||||
|
||||
script.setSourceCode(content);
|
||||
script.setName(name);
|
||||
script.setWorldId(QWebEngineScript::MainWorld);
|
||||
script.setInjectionPoint(QWebEngineScript::DocumentCreation);
|
||||
script.setRunsOnSubFrames(false);
|
||||
|
||||
scripts().insert(script);
|
||||
}
|
||||
|
||||
void DiscordPage::injectScriptFile(QString name, QString source) {
|
||||
QFile userscript(source);
|
||||
|
||||
if (!userscript.open(QIODevice::ReadOnly)) {
|
||||
@@ -52,33 +135,10 @@ void DiscordPage::injectScript(QString source) {
|
||||
userscript.errorString().toLatin1().constData());
|
||||
} else {
|
||||
QByteArray userscriptJs = userscript.readAll();
|
||||
|
||||
QWebEngineScript script;
|
||||
|
||||
script.setSourceCode(userscriptJs);
|
||||
script.setName("userscript.js");
|
||||
script.setWorldId(QWebEngineScript::MainWorld);
|
||||
script.setInjectionPoint(QWebEngineScript::DocumentCreation);
|
||||
script.setRunsOnSubFrames(false);
|
||||
|
||||
scripts().insert(script);
|
||||
injectScriptText(name, userscriptJs);
|
||||
}
|
||||
}
|
||||
|
||||
void DiscordPage::injectVersion(QString version) {
|
||||
QWebEngineScript script;
|
||||
|
||||
auto code = QString("window.discordScreenaudioVersion = '%1';").arg(version);
|
||||
|
||||
script.setSourceCode(code);
|
||||
script.setName("version.js");
|
||||
script.setWorldId(QWebEngineScript::MainWorld);
|
||||
script.setInjectionPoint(QWebEngineScript::DocumentCreation);
|
||||
script.setRunsOnSubFrames(false);
|
||||
|
||||
scripts().insert(script);
|
||||
}
|
||||
|
||||
void DiscordPage::featurePermissionRequested(const QUrl &securityOrigin,
|
||||
QWebEnginePage::Feature feature) {
|
||||
// Allow every permission asked
|
||||
@@ -87,8 +147,8 @@ void DiscordPage::featurePermissionRequested(const QUrl &securityOrigin,
|
||||
|
||||
if (feature == QWebEnginePage::Feature::MediaAudioCapture) {
|
||||
if (m_virtmicProcess.state() == QProcess::NotRunning) {
|
||||
qDebug() << "[virtmic] Starting Virtmic with no target to make sure "
|
||||
"Discord can find all the audio devices";
|
||||
qDebug(virtmicLog) << "Starting Virtmic with no target to make sure "
|
||||
"Discord can find all the audio devices";
|
||||
m_virtmicProcess.start(QApplication::arguments()[0],
|
||||
{"--virtmic", "None"});
|
||||
}
|
||||
@@ -119,7 +179,7 @@ QWebEnginePage *DiscordPage::createWindow(QWebEnginePage::WebWindowType type) {
|
||||
|
||||
void DiscordPage::stopVirtmic() {
|
||||
if (m_virtmicProcess.state() == QProcess::Running) {
|
||||
qDebug() << "[virtmic] Stopping Virtmic";
|
||||
qDebug(virtmicLog) << "Stopping Virtmic";
|
||||
m_virtmicProcess.kill();
|
||||
m_virtmicProcess.waitForFinished();
|
||||
}
|
||||
@@ -127,7 +187,7 @@ void DiscordPage::stopVirtmic() {
|
||||
|
||||
void DiscordPage::startVirtmic(QString target) {
|
||||
if (target != "None") {
|
||||
qDebug() << "[virtmic] Starting Virtmic with target" << target;
|
||||
qDebug(virtmicLog) << "Starting Virtmic with target" << target;
|
||||
m_virtmicProcess.start(QApplication::arguments()[0], {"--virtmic", target});
|
||||
}
|
||||
}
|
||||
@@ -143,8 +203,30 @@ void DiscordPage::javaScriptConsoleMessage(
|
||||
m_streamDialog.updateTargets();
|
||||
} else if (message == "!discord-screenaudio-stream-stopped") {
|
||||
stopVirtmic();
|
||||
} else if (message == "!discord-screenaudio-about") {
|
||||
#ifdef KXMLGUI
|
||||
m_helpMenu->aboutApplication();
|
||||
#endif
|
||||
} else if (message == "!discord-screenaudio-keybinds") {
|
||||
#ifdef KXMLGUI
|
||||
#ifdef KGLOBALACCEL
|
||||
m_shortcutsDialog->show();
|
||||
#else
|
||||
QMessageBox::information(MainWindow::instance(), "discord-screenaudio",
|
||||
"Keybinds are not supported on this platform "
|
||||
"(KGlobalAccel is not available).",
|
||||
QMessageBox::Ok);
|
||||
#endif
|
||||
#else
|
||||
QMessageBox::information(MainWindow::instance(), "discord-screenaudio",
|
||||
"Keybinds are not supported on this platform "
|
||||
"(KXmlGui and KGlobalAccel are not available).",
|
||||
QMessageBox::Ok);
|
||||
#endif
|
||||
} else if (message.startsWith("dsa: ")) {
|
||||
qDebug(userscriptLog) << message.mid(5).toUtf8().constData();
|
||||
} else {
|
||||
qDebug() << "[discord]" << message;
|
||||
qDebug(discordLog) << message;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -160,3 +242,13 @@ void DiscordPage::startStream(QString target, uint width, uint height,
|
||||
.arg(frameRate));
|
||||
});
|
||||
}
|
||||
|
||||
void DiscordPage::toggleMute() {
|
||||
qDebug(shortcutLog) << "Toggling mute";
|
||||
runJavaScript("window.discordScreenaudioToggleMute();");
|
||||
}
|
||||
|
||||
void DiscordPage::toggleDeafen() {
|
||||
qDebug(shortcutLog) << "Toggling deafen";
|
||||
runJavaScript("window.discordScreenaudioToggleDeafen();");
|
||||
}
|
||||
|
||||
@@ -3,6 +3,12 @@
|
||||
#include "streamdialog.h"
|
||||
#include "virtmic.h"
|
||||
|
||||
#ifdef KXMLGUI
|
||||
#include <KActionCollection>
|
||||
#include <KHelpMenu>
|
||||
#include <KShortcutsDialog>
|
||||
#endif
|
||||
|
||||
#include <QProcess>
|
||||
#include <QWebEngineFullScreenRequest>
|
||||
#include <QWebEnginePage>
|
||||
@@ -16,6 +22,13 @@ public:
|
||||
private:
|
||||
StreamDialog m_streamDialog;
|
||||
QProcess m_virtmicProcess;
|
||||
#ifdef KXMLGUI
|
||||
KHelpMenu *m_helpMenu;
|
||||
#ifdef KGLOBALACCEL
|
||||
KActionCollection *m_actionCollection;
|
||||
KShortcutsDialog *m_shortcutsDialog;
|
||||
#endif
|
||||
#endif
|
||||
bool acceptNavigationRequest(const QUrl &url,
|
||||
QWebEnginePage::NavigationType type,
|
||||
bool isMainFrame) override;
|
||||
@@ -24,10 +37,12 @@ private:
|
||||
javaScriptConsoleMessage(QWebEnginePage::JavaScriptConsoleMessageLevel level,
|
||||
const QString &message, int lineNumber,
|
||||
const QString &sourceID) override;
|
||||
void injectScript(QString source);
|
||||
void injectVersion(QString version);
|
||||
void injectScriptText(QString name, QString source);
|
||||
void injectScriptFile(QString name, QString content);
|
||||
void stopVirtmic();
|
||||
void startVirtmic(QString target);
|
||||
void toggleMute();
|
||||
void toggleDeafen();
|
||||
|
||||
private Q_SLOTS:
|
||||
void featurePermissionRequested(const QUrl &securityOrigin,
|
||||
|
||||
7
src/log.cpp
Normal file
7
src/log.cpp
Normal file
@@ -0,0 +1,7 @@
|
||||
#include "log.h"
|
||||
|
||||
Q_LOGGING_CATEGORY(mainLog, "main");
|
||||
Q_LOGGING_CATEGORY(discordLog, "discord");
|
||||
Q_LOGGING_CATEGORY(userscriptLog, "userscript");
|
||||
Q_LOGGING_CATEGORY(virtmicLog, "virtmic");
|
||||
Q_LOGGING_CATEGORY(shortcutLog, "shortcut");
|
||||
9
src/log.h
Normal file
9
src/log.h
Normal file
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include <QLoggingCategory>
|
||||
|
||||
Q_DECLARE_LOGGING_CATEGORY(mainLog);
|
||||
Q_DECLARE_LOGGING_CATEGORY(discordLog);
|
||||
Q_DECLARE_LOGGING_CATEGORY(userscriptLog);
|
||||
Q_DECLARE_LOGGING_CATEGORY(virtmicLog);
|
||||
Q_DECLARE_LOGGING_CATEGORY(shortcutLog);
|
||||
25
src/main.cpp
25
src/main.cpp
@@ -1,8 +1,13 @@
|
||||
#include "mainwindow.h"
|
||||
#include "virtmic.h"
|
||||
|
||||
#ifdef KXMLGUI
|
||||
#include <KAboutData>
|
||||
#endif
|
||||
|
||||
#include <QApplication>
|
||||
#include <QCommandLineParser>
|
||||
#include <QLoggingCategory>
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
QApplication app(argc, argv);
|
||||
@@ -10,6 +15,9 @@ int main(int argc, char *argv[]) {
|
||||
QApplication::setWindowIcon(
|
||||
QIcon(":assets/de.shorsh.discord-screenaudio.png"));
|
||||
QApplication::setApplicationVersion(DISCORD_SCEENAUDIO_VERSION_FULL);
|
||||
QApplication::setDesktopFileName("de.shorsh.discord-screenaudio");
|
||||
|
||||
qSetMessagePattern("[%{category}] %{message}");
|
||||
|
||||
QCommandLineParser parser;
|
||||
parser.setApplicationDescription(
|
||||
@@ -19,16 +27,25 @@ int main(int argc, char *argv[]) {
|
||||
QCommandLineOption virtmicOption("virtmic", "Start the Virtual Microphone",
|
||||
"target");
|
||||
parser.addOption(virtmicOption);
|
||||
#ifdef DEBUG
|
||||
parser.addOption(QCommandLineOption(
|
||||
"remote-debugging-port", "Chromium Remote Debugging Port", "port"));
|
||||
#endif
|
||||
QCommandLineOption degubOption("remote-debugging",
|
||||
"Open Chromium Remote Debugging on port 9222");
|
||||
parser.addOption(degubOption);
|
||||
|
||||
parser.process(app);
|
||||
|
||||
if (parser.isSet(virtmicOption)) {
|
||||
Virtmic::start(parser.value(virtmicOption));
|
||||
}
|
||||
|
||||
qputenv("QTWEBENGINE_CHROMIUM_FLAGS",
|
||||
"--enable-features=WebRTCPipeWireCapturer " +
|
||||
qgetenv("QTWEBENGINE_CHROMIUM_FLAGS"));
|
||||
|
||||
if (parser.isSet(degubOption))
|
||||
qputenv("QTWEBENGINE_CHROMIUM_FLAGS",
|
||||
"--remote-debugging-port=9222 " +
|
||||
qgetenv("QTWEBENGINE_CHROMIUM_FLAGS"));
|
||||
|
||||
MainWindow w;
|
||||
w.show();
|
||||
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
#include "mainwindow.h"
|
||||
#include "virtmic.h"
|
||||
|
||||
#ifdef KNOTIFICATIONS
|
||||
#include <KNotification>
|
||||
#endif
|
||||
|
||||
#include <QApplication>
|
||||
#include <QColor>
|
||||
#include <QComboBox>
|
||||
@@ -11,12 +15,18 @@
|
||||
#include <QSpacerItem>
|
||||
#include <QThread>
|
||||
#include <QUrl>
|
||||
#include <QWebEngineNotification>
|
||||
#include <QWebEngineProfile>
|
||||
#include <QWebEngineScript>
|
||||
#include <QWebEngineScriptCollection>
|
||||
#include <QWebEngineSettings>
|
||||
#include <QWidget>
|
||||
|
||||
MainWindow *MainWindow::m_instance = nullptr;
|
||||
|
||||
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) {
|
||||
assert(MainWindow::m_instance == nullptr);
|
||||
MainWindow::m_instance = this;
|
||||
setupWebView();
|
||||
resize(1000, 700);
|
||||
showMaximized();
|
||||
@@ -30,6 +40,23 @@ void MainWindow::setupWebView() {
|
||||
m_webView = new QWebEngineView(this);
|
||||
m_webView->setPage(page);
|
||||
|
||||
#ifdef KNOTIFICATIONS
|
||||
QWebEngineProfile::defaultProfile()->setNotificationPresenter(
|
||||
[&](std::unique_ptr<QWebEngineNotification> notificationInfo) {
|
||||
KNotification *notification = new KNotification("discordNotification");
|
||||
notification->setTitle(notificationInfo->title());
|
||||
notification->setText(notificationInfo->message());
|
||||
notification->setPixmap(QPixmap::fromImage(notificationInfo->icon()));
|
||||
notification->setDefaultAction("View");
|
||||
connect(notification, &KNotification::defaultActivated,
|
||||
[&, notificationInfo = std::move(notificationInfo)]() {
|
||||
notificationInfo->click();
|
||||
activateWindow();
|
||||
});
|
||||
notification->sendEvent();
|
||||
});
|
||||
#endif
|
||||
|
||||
setCentralWidget(m_webView);
|
||||
}
|
||||
|
||||
@@ -45,3 +72,5 @@ void MainWindow::fullScreenRequested(
|
||||
}
|
||||
|
||||
void MainWindow::closeEvent(QCloseEvent *event) { QApplication::quit(); }
|
||||
|
||||
MainWindow *MainWindow::instance() { return m_instance; }
|
||||
|
||||
@@ -15,6 +15,7 @@ class MainWindow : public QMainWindow {
|
||||
|
||||
public:
|
||||
explicit MainWindow(QWidget *parent = nullptr);
|
||||
static MainWindow *instance();
|
||||
|
||||
private:
|
||||
void setupWebView();
|
||||
@@ -23,6 +24,7 @@ private:
|
||||
DiscordPage *m_discordPage;
|
||||
void closeEvent(QCloseEvent *event) override;
|
||||
bool m_wasMaximized;
|
||||
static MainWindow *m_instance;
|
||||
|
||||
private Q_SLOTS:
|
||||
void fullScreenRequested(QWebEngineFullScreenRequest fullScreenRequest);
|
||||
|
||||
@@ -19,9 +19,19 @@ StreamDialog::StreamDialog() : QWidget() {
|
||||
targetLabel->setText("Which app do you want to stream sound from?");
|
||||
layout->addWidget(targetLabel);
|
||||
|
||||
auto targetHBox = new QHBoxLayout(this);
|
||||
layout->addLayout(targetHBox);
|
||||
|
||||
m_targetComboBox = new QComboBox(this);
|
||||
updateTargets();
|
||||
layout->addWidget(m_targetComboBox);
|
||||
targetHBox->addWidget(m_targetComboBox);
|
||||
|
||||
auto refreshTargetsButton = new QPushButton(this);
|
||||
refreshTargetsButton->setFixedSize(30, 30);
|
||||
refreshTargetsButton->setIcon(QIcon::fromTheme("view-refresh"));
|
||||
connect(refreshTargetsButton, &QPushButton::clicked, this,
|
||||
&StreamDialog::updateTargets);
|
||||
targetHBox->addWidget(refreshTargetsButton);
|
||||
|
||||
auto qualityLabel = new QLabel(this);
|
||||
qualityLabel->setText("Stream Quality");
|
||||
@@ -70,9 +80,17 @@ void StreamDialog::startStream() {
|
||||
}
|
||||
|
||||
void StreamDialog::updateTargets() {
|
||||
auto lastTarget = m_targetComboBox->currentText();
|
||||
|
||||
m_targetComboBox->clear();
|
||||
m_targetComboBox->addItem("None");
|
||||
m_targetComboBox->addItem("[None]");
|
||||
m_targetComboBox->addItem("[All Desktop Audio]");
|
||||
for (auto target : Virtmic::getTargets()) {
|
||||
m_targetComboBox->addItem(target);
|
||||
}
|
||||
|
||||
if (m_targetComboBox->findText(lastTarget) != -1)
|
||||
m_targetComboBox->setCurrentText(lastTarget);
|
||||
else
|
||||
m_targetComboBox->setCurrentText("[All Desktop Audio]");
|
||||
}
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
#include "virtmic.h"
|
||||
#include "log.h"
|
||||
|
||||
#include <rohrkabel/loop/main.hpp>
|
||||
#include <rohrkabel/registry/registry.hpp>
|
||||
|
||||
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);
|
||||
@@ -19,11 +22,11 @@ QVector<QString> getTargets() {
|
||||
if (global.type == pipewire::node::type) {
|
||||
auto node = reg.bind<pipewire::node>(global.id);
|
||||
auto info = node.info();
|
||||
auto name = QString::fromStdString(info.props["application.name"]);
|
||||
|
||||
if (info.props.count("node.name")) {
|
||||
auto name = QString::fromStdString(info.props["node.name"]);
|
||||
if (!targets.contains(name))
|
||||
targets.append(name);
|
||||
if (name != "" && !EXCLUDE_TARGETS.contains(name) &&
|
||||
!targets.contains(name)) {
|
||||
targets.append(name);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -64,20 +67,24 @@ void start(QString _target) {
|
||||
continue;
|
||||
|
||||
auto &parent = nodes.at(parent_id);
|
||||
auto name = parent.props["application.name"];
|
||||
|
||||
if (parent.props["node.name"].find(target) != std::string::npos) {
|
||||
std::cout << "[virtmic] "
|
||||
<< "Link : " << target << ":" << port_id << " -> ";
|
||||
|
||||
if (port.info().props["audio.channel"] == "FL") {
|
||||
links.emplace(port_id, core.create<pipewire::link_factory>(
|
||||
{virt_fl->info().id, port_id}));
|
||||
std::cout << "[virtmic] " << virt_fl->info().id << std::endl;
|
||||
} else {
|
||||
links.emplace(port_id, core.create<pipewire::link_factory>(
|
||||
{virt_fr->info().id, port_id}));
|
||||
std::cout << "[virtmic] " << virt_fr->info().id << std::endl;
|
||||
}
|
||||
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(
|
||||
parent.props["application.name"]))
|
||||
.arg(port_id)
|
||||
.arg(fl ? virt_fl->info().id
|
||||
: virt_fr->info().id)
|
||||
.toUtf8()
|
||||
.data();
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -93,7 +100,7 @@ void start(QString _target) {
|
||||
pipewire::node::type, pipewire::node::version,
|
||||
pipewire::update_strategy::none);
|
||||
|
||||
if (target == "None") {
|
||||
if (target == "[None]") {
|
||||
while (true) {
|
||||
main_loop.run();
|
||||
}
|
||||
@@ -105,9 +112,13 @@ void start(QString _target) {
|
||||
[&](const pipewire::global &global) {
|
||||
if (global.type == pipewire::node::type) {
|
||||
auto node = reg.bind<pipewire::node>(global.id);
|
||||
std::cout << "[virtmic] "
|
||||
<< "Added : " << node.info().props["node.name"]
|
||||
<< std::endl;
|
||||
if (!node.info().props.count("application.name"))
|
||||
return;
|
||||
qDebug(virtmicLog) << QString("Added: %1")
|
||||
.arg(QString::fromStdString(
|
||||
node.info().props["application.name"]))
|
||||
.toUtf8()
|
||||
.data();
|
||||
|
||||
if (!nodes.count(global.id)) {
|
||||
nodes.emplace(global.id, node.info());
|
||||
@@ -141,8 +152,11 @@ void start(QString _target) {
|
||||
[&](const std::uint32_t id) {
|
||||
if (nodes.count(id)) {
|
||||
auto info = nodes.at(id);
|
||||
std::cout << "[virtmic] "
|
||||
<< "Removed: " << info.props["node.name"] << std::endl;
|
||||
qDebug(virtmicLog) << QString("Removed: %1")
|
||||
.arg(QString::fromStdString(
|
||||
info.props["application.name"].data()))
|
||||
.toUtf8()
|
||||
.data();
|
||||
nodes.erase(id);
|
||||
}
|
||||
if (ports.count(id)) {
|
||||
|
||||
@@ -138,11 +138,12 @@ function(determine_version source_dir var_prefix)
|
||||
# | exact tag | 0.9.0 |
|
||||
# | exact tag, dirty | 0.9.0+git20180604 |
|
||||
# | after tag | 0.9.0+git20180604.1.085039f |
|
||||
# | no tag, version.txt | 0.9.0+git20180604.2ee02af |
|
||||
# | no tag, version.txt | 0.9.0 |
|
||||
# | no tag, no version.txt| 0.0.0+git20180604.2ee02af |
|
||||
string(TIMESTAMP date "%Y%m%d" UTC)
|
||||
set(version_suffix "")
|
||||
if (NOT ${local_prefix}_VERSION_NCOMMITS EQUAL 0)
|
||||
if (has_version_txt)
|
||||
elseif (NOT ${local_prefix}_VERSION_NCOMMITS EQUAL 0)
|
||||
# 0.9.0+git20150604.4.289818b
|
||||
string(APPEND version_suffix "+git${date}.${${local_prefix}_VERSION_NCOMMITS}.${${local_prefix}_VERSION_GIT_REV}")
|
||||
elseif ((NOT has_release_tag) AND ((NOT has_version_txt) OR ("${base_version}" STREQUAL "0.0.0") OR (NOT "${revision}" STREQUAL "")))
|
||||
|
||||
Reference in New Issue
Block a user