Compare commits
	
		
			5 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | d184823ee9 | ||
|  | dd2beed4eb | ||
|  | 3740553aba | ||
|  | 555f5c62f5 | ||
|  | af3c62c263 | 
| @@ -23,6 +23,16 @@ 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 | ||||
| @@ -62,6 +72,12 @@ 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) | ||||
|   | ||||
| @@ -11,7 +11,7 @@ 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 | ||||
| @@ -51,12 +51,14 @@ You have multiple options: | ||||
|  | ||||
| - Basic building tools | ||||
| - CMake | ||||
| - Qt5, QtWebEngine and Kf5Notifications | ||||
| - 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 libkf5notifications-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 | ||||
|   | ||||
| @@ -29,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> | ||||
|   | ||||
| @@ -135,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); | ||||
|     } | ||||
|   } | ||||
| @@ -149,6 +158,40 @@ setInterval(() => { | ||||
|   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" | ||||
|   | ||||
| @@ -1,10 +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> | ||||
| @@ -33,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(mainLog) << "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)) { | ||||
| @@ -53,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 | ||||
| @@ -144,6 +203,26 @@ 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 { | ||||
| @@ -163,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, | ||||
|   | ||||
| @@ -4,3 +4,4 @@ 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"); | ||||
|   | ||||
| @@ -6,3 +6,4 @@ 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); | ||||
|   | ||||
| @@ -1,6 +1,10 @@ | ||||
| #include "mainwindow.h" | ||||
| #include "virtmic.h" | ||||
|  | ||||
| #ifdef KXMLGUI | ||||
| #include <KAboutData> | ||||
| #endif | ||||
|  | ||||
| #include <QApplication> | ||||
| #include <QCommandLineParser> | ||||
| #include <QLoggingCategory> | ||||
| @@ -26,6 +30,7 @@ int main(int argc, char *argv[]) { | ||||
|   QCommandLineOption degubOption("remote-debugging", | ||||
|                                  "Open Chromium Remote Debugging on port 9222"); | ||||
|   parser.addOption(degubOption); | ||||
|  | ||||
|   parser.process(app); | ||||
|  | ||||
|   if (parser.isSet(virtmicOption)) { | ||||
|   | ||||
| @@ -22,7 +22,11 @@ | ||||
| #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(); | ||||
| @@ -68,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); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user