diff --git a/CMakeLists.txt b/CMakeLists.txt index 41df0fd..833258e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) diff --git a/README.md b/README.md index c7e2053..ee83eaa 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/assets/userscript.js b/assets/userscript.js index c7d2246..cf00169 100644 --- a/assets/userscript.js +++ b/assets/userscript.js @@ -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" diff --git a/src/discordpage.cpp b/src/discordpage.cpp index d099a35..9795e26 100644 --- a/src/discordpage.cpp +++ b/src/discordpage.cpp @@ -1,10 +1,25 @@ #include "discordpage.h" #include "log.h" +#include "mainwindow.h" #include "virtmic.h" +#ifdef KXMLGUI +#include +#include +#include +#include +#include + +#ifdef KGLOBALACCEL +#include +#endif + +#endif + #include #include #include +#include #include #include #include @@ -36,16 +51,82 @@ DiscordPage::DiscordPage(QWidget *parent) : QWebEnginePage(parent) { 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{}); + m_actionCollection->addAction("toggleDeafen", toggleDeafenAction); + KGlobalAccel::setGlobalShortcut(toggleDeafenAction, QList{}); + + 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 +134,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 +202,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 +241,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();"); +} diff --git a/src/discordpage.h b/src/discordpage.h index f40203a..071f744 100644 --- a/src/discordpage.h +++ b/src/discordpage.h @@ -3,6 +3,12 @@ #include "streamdialog.h" #include "virtmic.h" +#ifdef KXMLGUI +#include +#include +#include +#endif + #include #include #include @@ -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, diff --git a/src/log.cpp b/src/log.cpp index fef9213..fd85be4 100644 --- a/src/log.cpp +++ b/src/log.cpp @@ -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"); diff --git a/src/log.h b/src/log.h index f96eeba..22998ef 100644 --- a/src/log.h +++ b/src/log.h @@ -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); diff --git a/src/main.cpp b/src/main.cpp index 99d035d..e2cd74b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,6 +1,10 @@ #include "mainwindow.h" #include "virtmic.h" +#ifdef KXMLGUI +#include +#endif + #include #include #include @@ -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)) { diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index c44e458..bcf8f57 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -22,7 +22,11 @@ #include #include +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; } diff --git a/src/mainwindow.h b/src/mainwindow.h index 666dbc0..39721c8 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -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);