Compare commits

...

32 Commits

Author SHA1 Message Date
Malte Jürgens
bdabf68c9b add chromium flags for hardware acceleration 2022-10-15 20:28:38 +02:00
Malte Jürgens
7c8f72b8d8 fix 2022-10-11 22:17:53 +02:00
Malte Jürgens
27cdd9f9a5 code fixes 2022-10-11 19:48:57 +02:00
Malte Jürgens
d184823ee9 Enable smooth scrolling 2022-10-10 21:54:26 +02:00
Malte Jürgens
dd2beed4eb Implement Keybinds (#57) 2022-10-10 19:50:26 +00:00
Malte Jürgens
3740553aba Merge pull request #54 from D3SOX/improve-desktop-entry
Improve desktop entry
2022-09-29 16:47:49 +00:00
Nico
555f5c62f5 feat(desktop-entry): add categories and comment 2022-09-29 18:45:08 +02:00
Malte Jürgens
af3c62c263 update screenshot 2022-09-25 11:32:23 +02:00
Malte Jürgens
c4f6c33f9d display correct steam resolution 2022-09-21 17:32:41 +02:00
Malte Jürgens
758ffa8cb5 Update README (#48) 2022-09-18 12:04:04 +02:00
Malte Jürgens
753da8b1d3 automatically detect if kf5notifications is installed 2022-08-29 14:23:00 +02:00
Vortex Acherontic
5f37ab314f Changed location of openSUSE package (#34) 2022-08-21 07:21:18 +02:00
Malte Jürgens
c7bff3f428 add refresh button to stream dialog 2022-08-04 15:51:03 +02:00
Malte Jürgens
34e23f7187 cmake: use version from text file without suffix 2022-08-04 15:47:04 +02:00
Malte Jürgens
c325dd6798 allow sharing all desktop audio 2022-08-04 15:27:39 +02:00
Malte Jürgens
b42fde85d1 only allow to share application audio 2022-08-04 14:57:09 +02:00
Malte Jürgens
cc379ada85 fix metainfo again 2022-08-04 11:10:29 +02:00
Malte Jürgens
fdb6145bbd improve logging 2022-08-02 12:41:32 +02:00
Malte Jürgens
8bd4d1896a poll for enumerateDevices() to get available 2022-08-02 11:56:08 +02:00
Malte Jürgens
1a2d09fe3d remove useless main.h again 2022-08-02 11:46:37 +02:00
Malte Jürgens
f01c941c90 add timeout to getAudioDevice 2022-08-01 22:26:44 +02:00
Malte Jürgens
95d391e869 fix remote debugging 2022-08-01 19:59:01 +02:00
Malte Jürgens
af107168db update metainfo description 2022-07-31 21:12:53 +02:00
Malte Jürgens
e7e6c5d82b update readme 2022-07-30 21:14:56 +02:00
Malte Jürgens
ee0c66762d add --enable-features=WebRTCPipeWireCapturer
Co-authored-by: edisionnano <samantas5855@gmail.com>
2022-07-30 21:09:30 +02:00
LukeVader
c3483a42db disclaimer (#23)
* disclaimer

add a disclaimer that no responsibility is on the developers in the case of any repercussions of breaking discord TOS

* Update disclaimer

Co-authored-by: Malte Jürgens <maltejur@dismail.de>

Co-authored-by: Malte Jürgens <maltejur@dismail.de>
2022-07-29 21:01:11 +00:00
Malte Jürgens
d68797cebb make KF5Notifications optional 2022-07-29 16:31:04 +02:00
Malte Jürgens
ef221d4574 implement better notifications 2022-07-29 16:08:19 +02:00
Malte Jürgens
3fa2e3fa3c update installation instructions 2022-07-29 14:41:00 +02:00
Malte Jürgens
bda1bc0df6 fixed invisible stream dialog 2022-07-29 01:03:25 +02:00
Malte Jürgens
cb0f084d42 fix metainfo 2022-07-29 01:02:20 +02:00
Malte Jürgens
674ac146f2 use icon for window 2022-07-28 18:36:09 +02:00
19 changed files with 431 additions and 84 deletions

1
.gitignore vendored
View File

@@ -1 +1,2 @@
/build /build
.vscode

View File

@@ -18,12 +18,28 @@ find_package(Qt5 CONFIG REQUIRED COMPONENTS
WebEngineWidgets 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 set(discord-screenaudio_SRC
src/main.cpp src/main.cpp
src/mainwindow.cpp src/mainwindow.cpp
src/virtmic.cpp src/virtmic.cpp
src/discordpage.cpp src/discordpage.cpp
src/streamdialog.cpp src/streamdialog.cpp
src/log.cpp
resources.qrc resources.qrc
) )
@@ -52,6 +68,17 @@ add_executable(discord-screenaudio ${discord-screenaudio_SRC})
target_link_libraries(discord-screenaudio Qt5::Widgets Qt5::WebEngineWidgets rohrkabel) 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(TARGETS discord-screenaudio DESTINATION bin)
install(FILES assets/de.shorsh.discord-screenaudio.png DESTINATION ${CMAKE_INSTALL_PREFIX}/share/icons/hicolor/256x256/apps) 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) install(PROGRAMS assets/de.shorsh.discord-screenaudio.desktop DESTINATION ${CMAKE_INSTALL_PREFIX}/share/applications)

View File

@@ -1,13 +1,17 @@
# discord-screenaudio # discord-screenaudio
A very WIP custom discord client that supports streaming with audio on Linux, A custom discord client that supports streaming with audio on Linux, made
made possible by the possible by the
[great work](https://github.com/edisionnano/Screenshare-with-audio-on-Discord-with-Linux) [great work](https://github.com/edisionnano/Screenshare-with-audio-on-Discord-with-Linux)
of [@edisionnano](https://github.com/edisionnano) and the of [@edisionnano](https://github.com/edisionnano) and the
[Rohrkabel library](https://github.com/Soundux/rohrkabel) by [Rohrkabel library](https://github.com/Soundux/rohrkabel) by
[@Curve](https://github.com/Curve). [@Curve](https://github.com/Curve).
![Screenshot_20220718_194357](https://user-images.githubusercontent.com/48161361/179571245-11ea05f3-fb5e-4aef-9132-2736e122ef04.png) 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)).
![Screenshot_20220925_112945](https://user-images.githubusercontent.com/48161361/192137080-33466cf7-8c56-4373-90c6-01ea74b6fb83.png)
The purpose of this project is **not** to provide an alternative to the original 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 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 anything else, this client has way too many things that work less well than in
the original client. 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 ## Known Issues
- Only works with **PipeWire** - Only works with **PipeWire**
- Only works on **X11** - Can only share primary screen on X11 (no other screens or specific
- Can only share primary screen (no other screens or specific applications) (see applications) (see
[#1](https://github.com/maltejur/discord-screenaudio/issues/1)) [#1](https://github.com/maltejur/discord-screenaudio/issues/1))
## Installation ## Installation
If you are using Arch, you can build and install You have multiple options:
[from the AUR](https://aur.archlinux.org/packages/discord-screenaudio). If you
aren't, you will have to [build it yourself](#building-from-source). - 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 ## Building from Source
@@ -37,9 +54,11 @@ aren't, you will have to [build it yourself](#building-from-source).
- Qt5 and QtWebEngine - Qt5 and QtWebEngine
- **PipeWire** (it currently doesn't work with PulseAudio) - **PipeWire** (it currently doesn't work with PulseAudio)
- Git - Git
- _Kf5Notifications (optional, for better notifications)_
- _KXMLGui and KGlobalAccel (optional, for keybinds)_
On Debian: 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 ### Building

View File

@@ -2,5 +2,7 @@
Type=Application Type=Application
Name=discord-screenaudio Name=discord-screenaudio
Exec=discord-screenaudio Exec=discord-screenaudio
Comment=A custom discord client that supports streaming with audio on Linux.
Icon=de.shorsh.discord-screenaudio Icon=de.shorsh.discord-screenaudio
Categories=Network;InstantMessaging;
Terminal=false Terminal=false

View File

@@ -8,21 +8,28 @@
<release version="${DISCORD_SCEENAUDIO_VERSION_FULL}" timestamp="${TIMESTAMP}" /> <release version="${DISCORD_SCEENAUDIO_VERSION_FULL}" timestamp="${TIMESTAMP}" />
</releases> </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> <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>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> </description>
<launchable type="desktop-id"> <launchable type="desktop-id">
discord-screenaudio.desktop de.shorsh.discord-screenaudio.desktop
</launchable> </launchable>
<screenshots> <screenshots>
<screenshot type="default"> <screenshot type="default">
<image> <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> </image>
</screenshot> </screenshot>
</screenshots> </screenshots>
@@ -32,4 +39,12 @@
<provides> <provides>
<binary>discord-screenaudio</binary> <binary>discord-screenaudio</binary>
</provides> </provides>
<content_rating type="oars-1.0">
<content_attribute id="social-chat">intense</content_attribute>
<content_attribute id="social-info">intense</content_attribute>
<content_attribute id="social-audio">intense</content_attribute>
<content_attribute id="social-contacts">intense</content_attribute>
<content_attribute id="money-purchasing">mild</content_attribute>
</content_rating>
</component> </component>

View 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

View File

@@ -3,12 +3,25 @@
navigator.mediaDevices.chromiumGetDisplayMedia = navigator.mediaDevices.chromiumGetDisplayMedia =
navigator.mediaDevices.getDisplayMedia; navigator.mediaDevices.getDisplayMedia;
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
const getAudioDevice = async (nameOfAudioDevice) => { const getAudioDevice = async (nameOfAudioDevice) => {
await navigator.mediaDevices.getUserMedia({ await navigator.mediaDevices.getUserMedia({
audio: true, audio: true,
}); });
let devices = await navigator.mediaDevices.enumerateDevices(); let audioDevice;
let audioDevice = devices.find(({ label }) => label === nameOfAudioDevice); 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; return audioDevice;
}; };
@@ -99,6 +112,7 @@ setInterval(() => {
const initialDisplay = el.style.display; const initialDisplay = el.style.display;
window.discordScreenaudioStartStream = (width, height, frameRate) => { window.discordScreenaudioStartStream = (width, height, frameRate) => {
window.discordScreenaudioResolutionString = `${height}p ${frameRate}FPS`;
setGetDisplayMedia({ setGetDisplayMedia({
audio: true, audio: true,
video: { width, height, frameRate }, video: { width, height, frameRate },
@@ -121,12 +135,21 @@ setInterval(() => {
document.getElementsByClassName("dirscordScreenaudioAboutText").length == 0 document.getElementsByClassName("dirscordScreenaudioAboutText").length == 0
) { ) {
for (const el of document.getElementsByClassName("info-3pQQBb")) { 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.innerText = `discord-screenaudio ${window.discordScreenaudioVersion}`;
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";
aboutEl.classList.add("dirscordScreenaudioAboutText"); aboutEl.classList.add("dirscordScreenaudioAboutText");
aboutEl.style.cursor = "pointer";
el.appendChild(aboutEl); el.appendChild(aboutEl);
} }
} }
@@ -134,6 +157,48 @@ setInterval(() => {
// Remove stream settings if stream is active // Remove stream settings if stream is active
document.getElementById("manage-streams-change-windows")?.remove(); document.getElementById("manage-streams-change-windows")?.remove();
document.querySelector(`[aria-label="Stream Settings"]`)?.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); }, 500);
// Fix for broken discord notifications after restart // Fix for broken discord notifications after restart

View File

@@ -2,5 +2,6 @@
<RCC> <RCC>
<qresource> <qresource>
<file>assets/userscript.js</file> <file>assets/userscript.js</file>
<file>assets/de.shorsh.discord-screenaudio.png</file>
</qresource> </qresource>
</RCC> </RCC>

View File

@@ -1,9 +1,26 @@
#include "discordpage.h" #include "discordpage.h"
#include "log.h"
#include "mainwindow.h"
#include "virtmic.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 <QApplication>
#include <QDesktopServices> #include <QDesktopServices>
#include <QFile> #include <QFile>
#include <QMessageBox>
#include <QNetworkReply>
#include <QTimer> #include <QTimer>
#include <QWebChannel> #include <QWebChannel>
#include <QWebEngineScript> #include <QWebEngineScript>
@@ -32,46 +49,78 @@ DiscordPage::DiscordPage(QWidget *parent) : QWebEnginePage(parent) {
settings()->setAttribute(QWebEngineSettings::PlaybackRequiresUserGesture, settings()->setAttribute(QWebEngineSettings::PlaybackRequiresUserGesture,
false); false);
settings()->setAttribute(QWebEngineSettings::JavascriptCanOpenWindows, false); settings()->setAttribute(QWebEngineSettings::JavascriptCanOpenWindows, false);
settings()->setAttribute(QWebEngineSettings::ScrollAnimatorEnabled, true);
setUrl(QUrl("https://discord.com/app")); setUrl(QUrl("https://discord.com/app"));
injectScript(":/assets/userscript.js"); injectScriptFile("userscript.js", ":/assets/userscript.js");
injectVersion(QApplication::applicationVersion());
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, connect(&m_streamDialog, &StreamDialog::requestedStreamStart, this,
&DiscordPage::startStream); &DiscordPage::startStream);
} }
void DiscordPage::injectScript(QString source) { void DiscordPage::injectScriptText(QString name, QString content) {
qDebug() << "[main ] Injecting " << source; qDebug(mainLog) << "Injecting " << name;
QFile userscript(source);
if (!userscript.open(QIODevice::ReadOnly)) {
qFatal("Failed to load %s with error: %s", source.toLatin1().constData(),
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);
}
}
void DiscordPage::injectVersion(QString version) {
QWebEngineScript script; QWebEngineScript script;
auto code = QString("window.discordScreenaudioVersion = '%1';").arg(version); script.setSourceCode(content);
script.setName(name);
script.setSourceCode(code);
script.setName("version.js");
script.setWorldId(QWebEngineScript::MainWorld); script.setWorldId(QWebEngineScript::MainWorld);
script.setInjectionPoint(QWebEngineScript::DocumentCreation); script.setInjectionPoint(QWebEngineScript::DocumentCreation);
script.setRunsOnSubFrames(false); script.setRunsOnSubFrames(false);
@@ -79,6 +128,17 @@ void DiscordPage::injectVersion(QString version) {
scripts().insert(script); scripts().insert(script);
} }
void DiscordPage::injectScriptFile(QString name, QString source) {
QFile file(source);
if (!file.open(QIODevice::ReadOnly)) {
qFatal("Failed to load %s with error: %s", source.toLatin1().constData(),
file.errorString().toLatin1().constData());
} else {
injectScriptText(name, file.readAll());
}
}
void DiscordPage::featurePermissionRequested(const QUrl &securityOrigin, void DiscordPage::featurePermissionRequested(const QUrl &securityOrigin,
QWebEnginePage::Feature feature) { QWebEnginePage::Feature feature) {
// Allow every permission asked // Allow every permission asked
@@ -87,8 +147,8 @@ void DiscordPage::featurePermissionRequested(const QUrl &securityOrigin,
if (feature == QWebEnginePage::Feature::MediaAudioCapture) { if (feature == QWebEnginePage::Feature::MediaAudioCapture) {
if (m_virtmicProcess.state() == QProcess::NotRunning) { if (m_virtmicProcess.state() == QProcess::NotRunning) {
qDebug() << "[virtmic] Starting Virtmic with no target to make sure " qDebug(virtmicLog) << "Starting Virtmic with no target to make sure "
"Discord can find all the audio devices"; "Discord can find all the audio devices";
m_virtmicProcess.start(QApplication::arguments()[0], m_virtmicProcess.start(QApplication::arguments()[0],
{"--virtmic", "None"}); {"--virtmic", "None"});
} }
@@ -119,7 +179,7 @@ QWebEnginePage *DiscordPage::createWindow(QWebEnginePage::WebWindowType type) {
void DiscordPage::stopVirtmic() { void DiscordPage::stopVirtmic() {
if (m_virtmicProcess.state() == QProcess::Running) { if (m_virtmicProcess.state() == QProcess::Running) {
qDebug() << "[virtmic] Stopping Virtmic"; qDebug(virtmicLog) << "Stopping Virtmic";
m_virtmicProcess.kill(); m_virtmicProcess.kill();
m_virtmicProcess.waitForFinished(); m_virtmicProcess.waitForFinished();
} }
@@ -127,7 +187,7 @@ void DiscordPage::stopVirtmic() {
void DiscordPage::startVirtmic(QString target) { void DiscordPage::startVirtmic(QString target) {
if (target != "None") { 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}); m_virtmicProcess.start(QApplication::arguments()[0], {"--virtmic", target});
} }
} }
@@ -143,8 +203,30 @@ void DiscordPage::javaScriptConsoleMessage(
m_streamDialog.updateTargets(); m_streamDialog.updateTargets();
} else if (message == "!discord-screenaudio-stream-stopped") { } else if (message == "!discord-screenaudio-stream-stopped") {
stopVirtmic(); 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 { } else {
qDebug() << "[discord]" << message; qDebug(discordLog) << message;
} }
} }
@@ -160,3 +242,13 @@ void DiscordPage::startStream(QString target, uint width, uint height,
.arg(frameRate)); .arg(frameRate));
}); });
} }
void DiscordPage::toggleMute() {
qDebug(shortcutLog) << "Toggling mute";
runJavaScript("window.discordScreenaudioToggleMute();");
}
void DiscordPage::toggleDeafen() {
qDebug(shortcutLog) << "Toggling deafen";
runJavaScript("window.discordScreenaudioToggleDeafen();");
}

View File

@@ -3,6 +3,12 @@
#include "streamdialog.h" #include "streamdialog.h"
#include "virtmic.h" #include "virtmic.h"
#ifdef KXMLGUI
#include <KActionCollection>
#include <KHelpMenu>
#include <KShortcutsDialog>
#endif
#include <QProcess> #include <QProcess>
#include <QWebEngineFullScreenRequest> #include <QWebEngineFullScreenRequest>
#include <QWebEnginePage> #include <QWebEnginePage>
@@ -16,6 +22,13 @@ public:
private: private:
StreamDialog m_streamDialog; StreamDialog m_streamDialog;
QProcess m_virtmicProcess; QProcess m_virtmicProcess;
#ifdef KXMLGUI
KHelpMenu *m_helpMenu;
#ifdef KGLOBALACCEL
KActionCollection *m_actionCollection;
KShortcutsDialog *m_shortcutsDialog;
#endif
#endif
bool acceptNavigationRequest(const QUrl &url, bool acceptNavigationRequest(const QUrl &url,
QWebEnginePage::NavigationType type, QWebEnginePage::NavigationType type,
bool isMainFrame) override; bool isMainFrame) override;
@@ -24,10 +37,12 @@ private:
javaScriptConsoleMessage(QWebEnginePage::JavaScriptConsoleMessageLevel level, javaScriptConsoleMessage(QWebEnginePage::JavaScriptConsoleMessageLevel level,
const QString &message, int lineNumber, const QString &message, int lineNumber,
const QString &sourceID) override; const QString &sourceID) override;
void injectScript(QString source); void injectScriptText(QString name, QString content);
void injectVersion(QString version); void injectScriptFile(QString name, QString source);
void stopVirtmic(); void stopVirtmic();
void startVirtmic(QString target); void startVirtmic(QString target);
void toggleMute();
void toggleDeafen();
private Q_SLOTS: private Q_SLOTS:
void featurePermissionRequested(const QUrl &securityOrigin, void featurePermissionRequested(const QUrl &securityOrigin,

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

View File

@@ -1,13 +1,23 @@
#include "mainwindow.h" #include "mainwindow.h"
#include "virtmic.h" #include "virtmic.h"
#ifdef KXMLGUI
#include <KAboutData>
#endif
#include <QApplication> #include <QApplication>
#include <QCommandLineParser> #include <QCommandLineParser>
#include <QLoggingCategory>
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
QApplication app(argc, argv); QApplication app(argc, argv);
QApplication::setApplicationName("discord-screenaudio"); QApplication::setApplicationName("discord-screenaudio");
QApplication::setWindowIcon(
QIcon(":assets/de.shorsh.discord-screenaudio.png"));
QApplication::setApplicationVersion(DISCORD_SCEENAUDIO_VERSION_FULL); QApplication::setApplicationVersion(DISCORD_SCEENAUDIO_VERSION_FULL);
QApplication::setDesktopFileName("de.shorsh.discord-screenaudio");
qSetMessagePattern("[%{category}] %{message}");
QCommandLineParser parser; QCommandLineParser parser;
parser.setApplicationDescription( parser.setApplicationDescription(
@@ -17,16 +27,27 @@ int main(int argc, char *argv[]) {
QCommandLineOption virtmicOption("virtmic", "Start the Virtual Microphone", QCommandLineOption virtmicOption("virtmic", "Start the Virtual Microphone",
"target"); "target");
parser.addOption(virtmicOption); parser.addOption(virtmicOption);
#ifdef DEBUG QCommandLineOption degubOption("remote-debugging",
parser.addOption(QCommandLineOption( "Open Chromium Remote Debugging on port 9222");
"remote-debugging-port", "Chromium Remote Debugging Port", "port")); parser.addOption(degubOption);
#endif
parser.process(app); parser.process(app);
if (parser.isSet(virtmicOption)) { if (parser.isSet(virtmicOption)) {
Virtmic::start(parser.value(virtmicOption)); Virtmic::start(parser.value(virtmicOption));
} }
qputenv("QTWEBENGINE_CHROMIUM_FLAGS",
"--ignore-gpu-blacklist --enable-gpu-rasterization "
"--enable-native-gpu-memory-buffers --num-raster-threads=4 "
"--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; MainWindow w;
w.show(); w.show();

View File

@@ -1,6 +1,10 @@
#include "mainwindow.h" #include "mainwindow.h"
#include "virtmic.h" #include "virtmic.h"
#ifdef KNOTIFICATIONS
#include <KNotification>
#endif
#include <QApplication> #include <QApplication>
#include <QColor> #include <QColor>
#include <QComboBox> #include <QComboBox>
@@ -11,12 +15,18 @@
#include <QSpacerItem> #include <QSpacerItem>
#include <QThread> #include <QThread>
#include <QUrl> #include <QUrl>
#include <QWebEngineNotification>
#include <QWebEngineProfile>
#include <QWebEngineScript> #include <QWebEngineScript>
#include <QWebEngineScriptCollection> #include <QWebEngineScriptCollection>
#include <QWebEngineSettings> #include <QWebEngineSettings>
#include <QWidget> #include <QWidget>
MainWindow *MainWindow::m_instance = nullptr;
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) {
assert(MainWindow::m_instance == nullptr);
MainWindow::m_instance = this;
setupWebView(); setupWebView();
resize(1000, 700); resize(1000, 700);
showMaximized(); showMaximized();
@@ -30,6 +40,23 @@ void MainWindow::setupWebView() {
m_webView = new QWebEngineView(this); m_webView = new QWebEngineView(this);
m_webView->setPage(page); 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); setCentralWidget(m_webView);
} }
@@ -45,3 +72,5 @@ void MainWindow::fullScreenRequested(
} }
void MainWindow::closeEvent(QCloseEvent *event) { QApplication::quit(); } void MainWindow::closeEvent(QCloseEvent *event) { QApplication::quit(); }
MainWindow *MainWindow::instance() { return m_instance; }

View File

@@ -15,6 +15,7 @@ class MainWindow : public QMainWindow {
public: public:
explicit MainWindow(QWidget *parent = nullptr); explicit MainWindow(QWidget *parent = nullptr);
static MainWindow *instance();
private: private:
void setupWebView(); void setupWebView();
@@ -23,6 +24,7 @@ private:
DiscordPage *m_discordPage; DiscordPage *m_discordPage;
void closeEvent(QCloseEvent *event) override; void closeEvent(QCloseEvent *event) override;
bool m_wasMaximized; bool m_wasMaximized;
static MainWindow *m_instance;
private Q_SLOTS: private Q_SLOTS:
void fullScreenRequested(QWebEngineFullScreenRequest fullScreenRequest); void fullScreenRequested(QWebEngineFullScreenRequest fullScreenRequest);

View File

@@ -13,14 +13,25 @@ StreamDialog::StreamDialog() : QWidget() {
setAttribute(Qt::WA_QuitOnClose, false); setAttribute(Qt::WA_QuitOnClose, false);
auto layout = new QVBoxLayout(this); auto layout = new QVBoxLayout(this);
layout->setSizeConstraint(QLayout::SetFixedSize);
auto targetLabel = new QLabel(this); auto targetLabel = new QLabel(this);
targetLabel->setText("Which app do you want to stream sound from?"); targetLabel->setText("Which app do you want to stream sound from?");
layout->addWidget(targetLabel); layout->addWidget(targetLabel);
auto targetHBox = new QHBoxLayout(this);
layout->addLayout(targetHBox);
m_targetComboBox = new QComboBox(this); m_targetComboBox = new QComboBox(this);
updateTargets(); 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); auto qualityLabel = new QLabel(this);
qualityLabel->setText("Stream Quality"); qualityLabel->setText("Stream Quality");
@@ -57,7 +68,6 @@ StreamDialog::StreamDialog() : QWidget() {
setLayout(layout); setLayout(layout);
setWindowTitle("discord-screenaudio Stream Dialog"); setWindowTitle("discord-screenaudio Stream Dialog");
setFixedSize(0, 0);
} }
void StreamDialog::startStream() { void StreamDialog::startStream() {
@@ -70,9 +80,17 @@ void StreamDialog::startStream() {
} }
void StreamDialog::updateTargets() { void StreamDialog::updateTargets() {
auto lastTarget = m_targetComboBox->currentText();
m_targetComboBox->clear(); m_targetComboBox->clear();
m_targetComboBox->addItem("None"); m_targetComboBox->addItem("[None]");
m_targetComboBox->addItem("[All Desktop Audio]");
for (auto target : Virtmic::getTargets()) { for (auto target : Virtmic::getTargets()) {
m_targetComboBox->addItem(target); m_targetComboBox->addItem(target);
} }
if (m_targetComboBox->findText(lastTarget) != -1)
m_targetComboBox->setCurrentText(lastTarget);
else
m_targetComboBox->setCurrentText("[All Desktop Audio]");
} }

View File

@@ -1,10 +1,13 @@
#include "virtmic.h" #include "virtmic.h"
#include "log.h"
#include <rohrkabel/loop/main.hpp> #include <rohrkabel/loop/main.hpp>
#include <rohrkabel/registry/registry.hpp> #include <rohrkabel/registry/registry.hpp>
namespace Virtmic { namespace Virtmic {
const QStringList EXCLUDE_TARGETS{"Chromium input", "discord-screenaudio"};
QVector<QString> getTargets() { QVector<QString> getTargets() {
auto main_loop = pipewire::main_loop(); auto main_loop = pipewire::main_loop();
auto context = pipewire::context(main_loop); auto context = pipewire::context(main_loop);
@@ -19,11 +22,11 @@ QVector<QString> getTargets() {
if (global.type == pipewire::node::type) { if (global.type == pipewire::node::type) {
auto node = reg.bind<pipewire::node>(global.id); auto node = reg.bind<pipewire::node>(global.id);
auto info = node.info(); auto info = node.info();
auto name = QString::fromStdString(info.props["application.name"]);
if (info.props.count("node.name")) { if (name != "" && !EXCLUDE_TARGETS.contains(name) &&
auto name = QString::fromStdString(info.props["node.name"]); !targets.contains(name)) {
if (!targets.contains(name)) targets.append(name);
targets.append(name);
} }
} }
}); });
@@ -64,20 +67,24 @@ void start(QString _target) {
continue; continue;
auto &parent = nodes.at(parent_id); auto &parent = nodes.at(parent_id);
auto name = parent.props["application.name"];
if (parent.props["node.name"].find(target) != std::string::npos) { if (name == target ||
std::cout << "[virtmic] " (target == "[All Desktop Audio]" &&
<< "Link : " << target << ":" << port_id << " -> "; !EXCLUDE_TARGETS.contains(QString::fromStdString(name)))) {
auto fl = port.info().props["audio.channel"] == "FL";
if (port.info().props["audio.channel"] == "FL") { links.emplace(
links.emplace(port_id, core.create<pipewire::link_factory>( port_id,
{virt_fl->info().id, port_id})); core.create<pipewire::link_factory>(
std::cout << "[virtmic] " << virt_fl->info().id << std::endl; {fl ? virt_fl->info().id : virt_fr->info().id, port_id}));
} else { qDebug(virtmicLog) << QString("Link: %1:%2 -> %3")
links.emplace(port_id, core.create<pipewire::link_factory>( .arg(QString::fromStdString(
{virt_fr->info().id, port_id})); parent.props["application.name"]))
std::cout << "[virtmic] " << virt_fr->info().id << std::endl; .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::node::type, pipewire::node::version,
pipewire::update_strategy::none); pipewire::update_strategy::none);
if (target == "None") { if (target == "[None]") {
while (true) { while (true) {
main_loop.run(); main_loop.run();
} }
@@ -105,9 +112,13 @@ void start(QString _target) {
[&](const pipewire::global &global) { [&](const pipewire::global &global) {
if (global.type == pipewire::node::type) { if (global.type == pipewire::node::type) {
auto node = reg.bind<pipewire::node>(global.id); auto node = reg.bind<pipewire::node>(global.id);
std::cout << "[virtmic] " if (!node.info().props.count("application.name"))
<< "Added : " << node.info().props["node.name"] return;
<< std::endl; qDebug(virtmicLog) << QString("Added: %1")
.arg(QString::fromStdString(
node.info().props["application.name"]))
.toUtf8()
.data();
if (!nodes.count(global.id)) { if (!nodes.count(global.id)) {
nodes.emplace(global.id, node.info()); nodes.emplace(global.id, node.info());
@@ -141,8 +152,11 @@ void start(QString _target) {
[&](const std::uint32_t id) { [&](const std::uint32_t id) {
if (nodes.count(id)) { if (nodes.count(id)) {
auto info = nodes.at(id); auto info = nodes.at(id);
std::cout << "[virtmic] " qDebug(virtmicLog) << QString("Removed: %1")
<< "Removed: " << info.props["node.name"] << std::endl; .arg(QString::fromStdString(
info.props["application.name"].data()))
.toUtf8()
.data();
nodes.erase(id); nodes.erase(id);
} }
if (ports.count(id)) { if (ports.count(id)) {

1
submodules/Vencord Submodule

Submodule submodules/Vencord added at 0d996633f2

View File

@@ -138,11 +138,12 @@ function(determine_version source_dir var_prefix)
# | exact tag | 0.9.0 | # | exact tag | 0.9.0 |
# | exact tag, dirty | 0.9.0+git20180604 | # | exact tag, dirty | 0.9.0+git20180604 |
# | after tag | 0.9.0+git20180604.1.085039f | # | 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 | # | no tag, no version.txt| 0.0.0+git20180604.2ee02af |
string(TIMESTAMP date "%Y%m%d" UTC) string(TIMESTAMP date "%Y%m%d" UTC)
set(version_suffix "") 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 # 0.9.0+git20150604.4.289818b
string(APPEND version_suffix "+git${date}.${${local_prefix}_VERSION_NCOMMITS}.${${local_prefix}_VERSION_GIT_REV}") 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 ""))) elseif ((NOT has_release_tag) AND ((NOT has_version_txt) OR ("${base_version}" STREQUAL "0.0.0") OR (NOT "${revision}" STREQUAL "")))