From 1477a9d4c043bbab3308a333d91afa539bdb922d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Malte=20J=C3=BCrgens?= Date: Sun, 12 Feb 2023 17:20:33 +0100 Subject: [PATCH] use qtwebchannel to communicate with userscript --- CMakeLists.txt | 1 + assets/userscript.js | 427 ++++++++++++++++++++++--------------------- src/discordpage.cpp | 172 +---------------- src/discordpage.h | 26 +-- src/streamdialog.cpp | 6 +- src/streamdialog.h | 4 +- src/userscript.cpp | 164 +++++++++++++++++ src/userscript.h | 69 +++++++ 8 files changed, 469 insertions(+), 400 deletions(-) create mode 100644 src/userscript.cpp create mode 100644 src/userscript.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 18b3df7..9feccc8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -43,6 +43,7 @@ set(discord-screenaudio_SRC src/discordpage.cpp src/streamdialog.cpp src/log.cpp + src/userscript.cpp resources.qrc ) diff --git a/assets/userscript.js b/assets/userscript.js index 28aaa7e..254ae6d 100644 --- a/assets/userscript.js +++ b/assets/userscript.js @@ -1,5 +1,3 @@ -// From v0.4 - navigator.mediaDevices.chromiumGetDisplayMedia = navigator.mediaDevices.getDisplayMedia; @@ -16,12 +14,12 @@ const getAudioDevice = async (nameOfAudioDevice) => { 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` + userscript.log( + `Did not find '${nameOfAudioDevice}', trying again in 100ms` ); await sleep(100); } - console.log(`dsa: Found '${nameOfAudioDevice}'`); + userscript.log(`Found '${nameOfAudioDevice}'`); return audioDevice; }; @@ -71,6 +69,13 @@ function setGetDisplayMedia(video = true, overrideArgs = undefined) { setGetDisplayMedia(); +let userscript; +let muteBtn; +let deafenBtn; +let streamStartBtn; +let streamStartBtnInitialDisplay; +let streamStartBtnClone; +let resolutionString; const clonedElements = []; const hiddenElements = []; let wasStreamActive = false; @@ -121,208 +126,216 @@ function createSwitch(text, enabled, onClick) { return container; } -setInterval(() => { - const streamActive = - document.getElementsByClassName("panel-2ZFCRb activityPanel-9icbyU") - .length > 0; - - if (!streamActive && wasStreamActive) - console.log("!discord-screenaudio-stream-stopped"); - wasStreamActive = streamActive; - - if (streamActive) { - clonedElements.forEach((el) => { - el.remove(); - }); - clonedElements.length = 0; - - hiddenElements.forEach((el) => { - el.style.display = "block"; - }); - hiddenElements.length = 0; - } else { - for (const el of [ - document.getElementsByClassName("actionButtons-2vEOUh")?.[0]?.children[1], - document.querySelector( - ".wrapper-3t3Yqv > div > div > div > div > .controlButton-2PMNom" - ), - ]) { - if (!el) continue; - if (el.classList.contains("discord-screenaudio-cloned")) continue; - el.classList.add("discord-screenaudio-cloned"); - elClone = el.cloneNode(true); - elClone.title = "Share Your Screen with Audio"; - elClone.addEventListener("click", () => { - console.log("!discord-screenaudio-start-stream"); - }); - - const initialDisplay = el.style.display; - - window.discordScreenaudioStartStream = ( - video, - width, - height, - frameRate - ) => { - window.discordScreenaudioResolutionString = video - ? `${height}p ${frameRate}FPS` - : "Audio Only"; - setGetDisplayMedia(video, { - audio: true, - video: { width, height, frameRate }, - }); - el.click(); - el.style.display = initialDisplay; - elClone.remove(); - }; - - el.style.display = "none"; - el.parentNode.insertBefore(elClone, el); - - clonedElements.push(elClone); - hiddenElements.push(el); - } - } - - // Add about text in settings - if ( - document.getElementsByClassName("dirscordScreenaudioAboutText").length == 0 - ) { - for (const el of document.getElementsByClassName("info-3pQQBb")) { - 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); - } - } - - // 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 browserNotice-1u-Y5o" - ).length - ) { - const el = document - .getElementById("keybinds-tab") - .getElementsByClassName("children-1xdcWE")[0]; - const div = document.createElement("div"); - div.style.marginBottom = "50px"; - div.appendChild( - createButton("Edit Global Keybinds", () => { - console.log("!discord-screenaudio-keybinds"); - }) - ); - el.innerHTML = ""; - el.appendChild(div); - } - - const buttonContainer = - document.getElementsByClassName("container-YkUktl")[0]; - if (!buttonContainer) { - console.log( - "dsa: Cannot locate Mute/Deafen/Settings button container, please report this on GitHub" - ); - } - - const muteBtn = buttonContainer - ? buttonContainer.getElementsByClassName( - "button-12Fmur enabled-9OeuTA button-f2h6uQ lookBlank-21BCro colorBrand-I6CyqQ grow-2sR_-F" - )[0] - : null; - window.discordScreenaudioToggleMute = () => muteBtn && muteBtn.click(); - - const deafenBtn = buttonContainer - ? buttonContainer.getElementsByClassName( - "button-12Fmur enabled-9OeuTA button-f2h6uQ lookBlank-21BCro colorBrand-I6CyqQ grow-2sR_-F" - )[1] - : null; - - window.discordScreenaudioToggleDeafen = () => deafenBtn && deafenBtn.click(); - - if (window.discordScreenaudioResolutionString) { - for (const el of document.getElementsByClassName( - "qualityIndicator-39wQDy" - )) { - el.innerHTML = window.discordScreenaudioResolutionString; - } - } - - const accountTab = document.getElementById("my-account-tab"); - if (accountTab) { - const discordScreenaudioSettings = document.getElementById( - "discord-screenaudio-settings" - ); - if (!discordScreenaudioSettings) { - const firstDivider = accountTab.getElementsByClassName( - "divider-3nqZNm marginTop40-Q4o1tS" - )[0]; - if (firstDivider) { - const section = document.createElement("div"); - section.className = "marginTop40-Q4o1tS"; - section.id = "discord-screenaudio-settings"; - - const title = document.createElement("h2"); - title.className = - "h1-3iMExa title-lXcL8p defaultColor-3Olr-9 defaultMarginh1-1UYutH"; - title.innerText = "discord-screenaudio"; - section.appendChild(title); - - section.appendChild( - createButton("Edit Global Keybinds", () => { - console.log("!discord-screenaudio-keybinds"); - }) - ); - - section.appendChild( - createSwitch( - "Move discord-screenaudio to the system tray instead of closing", - window.discordScreenaudioTrayEnabled, - (enabled) => { - window.discordScreenaudioTrayEnabled = enabled; - console.log(`!discord-screenaudio-tray-${enabled}`); - } - ) - ); - - section.appendChild( - createSwitch( - "Start discord-screenaudio hidden to tray", - window.discordScreenaudioStartHidden, - (hidden) => { - window.discordScreenaudioStartHidden = hidden; - console.log(`!discord-screenaudio-starthidden-${hidden}`); - } - ) - ); - - const divider = document.createElement("div"); - divider.className = "divider-3nqZNm marginTop40-Q4o1tS"; - - firstDivider.after(section); - section.after(divider); - } - } - } -}, 500); - // Fix for broken discord notifications after restart // (https://github.com/maltejur/discord-screenaudio/issues/17) Notification.requestPermission(); + +setTimeout(() => { + new QWebChannel(qt.webChannelTransport, (channel) => { + userscript = channel.objects.userscript; + main(); + }); +}); + +function main() { + userscript.muteToggled.connect(() => { + muteBtn && muteBtn.click(); + }); + + userscript.deafenToggled.connect(() => { + deafenBtn && deafenBtn.click(); + }); + + userscript.streamStarted.connect((video, width, height, frameRate) => { + resolutionString = video ? `${height}p ${frameRate}FPS` : "Audio Only"; + setGetDisplayMedia(video, { + audio: true, + video: { width, height, frameRate }, + }); + streamStartBtn.click(); + streamStartBtn.style.display = streamStartBtnInitialDisplay; + streamStartBtnClone.remove(); + }); + + setInterval(async () => { + const streamActive = + document.getElementsByClassName("panel-2ZFCRb activityPanel-9icbyU") + .length > 0; + + if (!streamActive && wasStreamActive) userscript.stopVirtmic(); + wasStreamActive = streamActive; + + if (streamActive) { + clonedElements.forEach((el) => { + el.remove(); + }); + clonedElements.length = 0; + + hiddenElements.forEach((el) => { + el.style.display = "block"; + }); + hiddenElements.length = 0; + } else { + for (const el of [ + document.getElementsByClassName("actionButtons-2vEOUh")?.[0] + ?.children[1], + document.querySelector( + ".wrapper-3t3Yqv > div > div > div > div > .controlButton-2PMNom" + ), + ]) { + if (!el) continue; + if (el.classList.contains("discord-screenaudio-cloned")) continue; + streamStartBtn = el; + streamStartBtn.classList.add("discord-screenaudio-cloned"); + + streamStartBtnClone = streamStartBtn.cloneNode(true); + streamStartBtnClone.title = "Share Your Screen with Audio"; + streamStartBtnClone.addEventListener("click", () => { + userscript.showStreamDialog(); + }); + + streamStartBtnInitialDisplay = streamStartBtn.style.display; + + streamStartBtn.style.display = "none"; + streamStartBtn.parentNode.insertBefore(streamStartBtnClone, el); + + clonedElements.push(streamStartBtnClone); + hiddenElements.push(streamStartBtn); + } + } + + // Add about text in settings + if ( + document.getElementsByClassName("dirscordScreenaudioAboutText").length == + 0 + ) { + for (const el of document.getElementsByClassName("info-3pQQBb")) { + let aboutEl; + if (userscript.kxmlgui) { + aboutEl = document.createElement("a"); + aboutEl.addEventListener("click", () => { + userscript.showHelpMenu(); + }); + } else { + aboutEl = document.createElement("div"); + } + aboutEl.innerText = `discord-screenaudio ${userscript.version}`; + 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); + } + } + + // 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 browserNotice-1u-Y5o" + ).length + ) { + const el = document + .getElementById("keybinds-tab") + .getElementsByClassName("children-1xdcWE")[0]; + const div = document.createElement("div"); + div.style.marginBottom = "50px"; + div.appendChild( + createButton("Edit Global Keybinds", () => { + userscript.log("!discord-screenaudio-keybinds"); + }) + ); + el.innerHTML = ""; + el.appendChild(div); + } + + const buttonContainer = + document.getElementsByClassName("container-YkUktl")[0]; + if (!buttonContainer) { + userscript.log( + "Cannot locate Mute/Deafen/Settings button container, please report this on GitHub" + ); + } + + muteBtn = buttonContainer + ? buttonContainer.getElementsByClassName( + "button-12Fmur enabled-9OeuTA button-f2h6uQ lookBlank-21BCro colorBrand-I6CyqQ grow-2sR_-F" + )[0] + : null; + + deafenBtn = buttonContainer + ? buttonContainer.getElementsByClassName( + "button-12Fmur enabled-9OeuTA button-f2h6uQ lookBlank-21BCro colorBrand-I6CyqQ grow-2sR_-F" + )[1] + : null; + + if (resolutionString) { + for (const el of document.getElementsByClassName( + "qualityIndicator-39wQDy" + )) { + el.innerHTML = resolutionString; + } + } + + const accountTab = document.getElementById("my-account-tab"); + if (accountTab) { + const discordScreenaudioSettings = document.getElementById( + "discord-screenaudio-settings" + ); + if (!discordScreenaudioSettings) { + const firstDivider = accountTab.getElementsByClassName( + "divider-3nqZNm marginTop40-Q4o1tS" + )[0]; + if (firstDivider) { + const section = document.createElement("div"); + section.className = "marginTop40-Q4o1tS"; + section.id = "discord-screenaudio-settings"; + + const title = document.createElement("h2"); + title.className = + "h1-3iMExa title-lXcL8p defaultColor-3Olr-9 defaultMarginh1-1UYutH"; + title.innerText = "discord-screenaudio"; + section.appendChild(title); + + section.appendChild( + createButton("Edit Global Keybinds", () => { + userscript.log("!discord-screenaudio-keybinds"); + }) + ); + + section.appendChild( + createSwitch( + "Move discord-screenaudio to the system tray instead of closing", + await userscript.getBoolPref("trayIcon", false), + (enabled) => { + userscript.setTrayIcon(enabled); + } + ) + ); + + section.appendChild( + createSwitch( + "Start discord-screenaudio hidden to tray", + await userscript.getPref("startHidden", false), + (hidden) => { + userscript.setPref("startHidden", hidden); + } + ) + ); + + const divider = document.createElement("div"); + divider.className = "divider-3nqZNm marginTop40-Q4o1tS"; + + firstDivider.after(section); + section.after(divider); + } + } + } + }, 500); +} diff --git a/src/discordpage.cpp b/src/discordpage.cpp index 01a699e..2739a19 100644 --- a/src/discordpage.cpp +++ b/src/discordpage.cpp @@ -3,19 +3,6 @@ #include "mainwindow.h" #include "virtmic.h" -#ifdef KXMLGUI -#include -#include -#include -#include -#include - -#ifdef KGLOBALACCEL -#include -#endif - -#endif - #include #include #include @@ -29,7 +16,6 @@ DiscordPage::DiscordPage(QWidget *parent) : QWebEnginePage(parent) { setBackgroundColor(QColor("#202225")); - m_virtmicProcess.setProcessChannelMode(QProcess::ForwardedChannels); connect(this, &QWebEnginePage::featurePermissionRequested, this, &DiscordPage::featurePermissionRequested); @@ -51,77 +37,14 @@ DiscordPage::DiscordPage(QWidget *parent) : QWebEnginePage(parent) { settings()->setAttribute(QWebEngineSettings::JavascriptCanOpenWindows, false); settings()->setAttribute(QWebEngineSettings::ScrollAnimatorEnabled, true); + injectScriptFile("qwebchannel.js", ":/qtwebchannel/qwebchannel.js"); + setUrl(QUrl("https://discord.com/app")); + setWebChannel(new QWebChannel(this)); + webChannel()->registerObject("userscript", &m_userScript); + injectScriptFile("userscript.js", ":/assets/userscript.js"); - - injectScriptText("vars.js", - QString("window.discordScreenaudioVersion = '%1'; " - "window.discordScreenaudioTrayEnabled = %2; " - "window.discordScreenaudioStartHidden = %3;") - .arg(QApplication::applicationVersion()) - .arg(MainWindow::instance() - ->settings() - ->value("trayIcon", false) - .toBool()) - .arg(MainWindow::instance() - ->settings() - ->value("startHidden", false) - .toBool())); - -#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::injectScriptText(QString name, QString content) { @@ -156,11 +79,10 @@ void DiscordPage::featurePermissionRequested(const QUrl &securityOrigin, QWebEnginePage::PermissionGrantedByUser); if (feature == QWebEnginePage::Feature::MediaAudioCapture) { - if (m_virtmicProcess.state() == QProcess::NotRunning) { + if (!m_userScript.isVirtmicRunning()) { 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"}); + m_userScript.startVirtmic("None"); } } } @@ -187,86 +109,8 @@ QWebEnginePage *DiscordPage::createWindow(QWebEnginePage::WebWindowType type) { return new ExternalPage; } -void DiscordPage::stopVirtmic() { - if (m_virtmicProcess.state() == QProcess::Running) { - qDebug(virtmicLog) << "Stopping Virtmic"; - m_virtmicProcess.kill(); - m_virtmicProcess.waitForFinished(); - } -} - -void DiscordPage::startVirtmic(QString target) { - qDebug(virtmicLog) << "Starting Virtmic with target" << target; - m_virtmicProcess.start(QApplication::arguments()[0], {"--virtmic", target}); -} - void DiscordPage::javaScriptConsoleMessage( QWebEnginePage::JavaScriptConsoleMessageLevel level, const QString &message, int lineNumber, const QString &sourceID) { - if (message == "!discord-screenaudio-start-stream") { - if (m_streamDialog.isHidden()) - m_streamDialog.setHidden(false); - else - m_streamDialog.activateWindow(); - 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 == "!discord-screenaudio-tray-true") { - MainWindow::instance()->setTrayIcon(true); - } else if (message == "!discord-screenaudio-tray-false") { - MainWindow::instance()->setTrayIcon(false); - } else if (message == "!discord-screenaudio-starthidden-true") { - MainWindow::instance()->settings()->setValue("startHidden", true); - } else if (message == "!discord-screenaudio-starthidden-false") { - MainWindow::instance()->settings()->setValue("startHidden", false); - } else if (message.startsWith("dsa: ")) { - qDebug(userscriptLog) << message.mid(5).toUtf8().constData(); - } else { - qDebug(discordLog) << message; - } -} - -void DiscordPage::startStream(bool video, bool audio, uint width, uint height, - uint frameRate, QString target) { - stopVirtmic(); - startVirtmic(audio ? target : "[None]"); - // Wait a bit for the virtmic to start - QTimer::singleShot(200, [=]() { - runJavaScript( - QString("window.discordScreenaudioStartStream(%1, %2, %3, %4);") - .arg(video) - .arg(video ? width : 32) - .arg(video ? height : 16) - .arg(video ? frameRate : 1)); - }); -} - -void DiscordPage::toggleMute() { - qDebug(shortcutLog) << "Toggling mute"; - runJavaScript("window.discordScreenaudioToggleMute();"); -} - -void DiscordPage::toggleDeafen() { - qDebug(shortcutLog) << "Toggling deafen"; - runJavaScript("window.discordScreenaudioToggleDeafen();"); + qDebug(discordLog) << message; } diff --git a/src/discordpage.h b/src/discordpage.h index 3668b2c..cc0da53 100644 --- a/src/discordpage.h +++ b/src/discordpage.h @@ -1,15 +1,7 @@ #pragma once -#include "streamdialog.h" -#include "virtmic.h" +#include "userscript.h" -#ifdef KXMLGUI -#include -#include -#include -#endif - -#include #include #include @@ -20,15 +12,7 @@ public: explicit DiscordPage(QWidget *parent = nullptr); private: - StreamDialog m_streamDialog; - QProcess m_virtmicProcess; -#ifdef KXMLGUI - KHelpMenu *m_helpMenu; -#ifdef KGLOBALACCEL - KActionCollection *m_actionCollection; - KShortcutsDialog *m_shortcutsDialog; -#endif -#endif + UserScript m_userScript; bool acceptNavigationRequest(const QUrl &url, QWebEnginePage::NavigationType type, bool isMainFrame) override; @@ -39,16 +23,10 @@ private: const QString &sourceID) override; void injectScriptText(QString name, QString content); void injectScriptFile(QString name, QString source); - void stopVirtmic(); - void startVirtmic(QString target); - void toggleMute(); - void toggleDeafen(); private Q_SLOTS: void featurePermissionRequested(const QUrl &securityOrigin, QWebEnginePage::Feature feature); - void startStream(bool video, bool audio, uint width, uint height, - uint frameRate, QString target); }; // Will immediately get destroyed again but is needed for navigation to diff --git a/src/streamdialog.cpp b/src/streamdialog.cpp index ba25f58..b1ebc2b 100644 --- a/src/streamdialog.cpp +++ b/src/streamdialog.cpp @@ -98,9 +98,9 @@ StreamDialog::StreamDialog() : QWidget() { void StreamDialog::startStream() { auto resolution = m_resolutionComboBox->currentData().toString().split('x'); emit requestedStreamStart(m_videoGroupBox->isChecked(), - m_audioGroupBox->isChecked(), - resolution[0].toUInt(), resolution[1].toUInt(), - m_framerateComboBox->currentData().toUInt(), + m_audioGroupBox->isChecked(), resolution[0].toInt(), + resolution[1].toInt(), + m_framerateComboBox->currentData().toInt(), m_targetComboBox->currentText()); setHidden(true); } diff --git a/src/streamdialog.h b/src/streamdialog.h index 8fab813..0154a8d 100644 --- a/src/streamdialog.h +++ b/src/streamdialog.h @@ -19,8 +19,8 @@ private: QGroupBox *m_audioGroupBox; Q_SIGNALS: - void requestedStreamStart(bool video, bool audio, uint width, uint height, - uint frameRate, QString target); + void requestedStreamStart(bool video, bool audio, int width, int height, + int frameRate, QString target); public Q_SLOTS: void updateTargets(); diff --git a/src/userscript.cpp b/src/userscript.cpp new file mode 100644 index 0000000..e6fc764 --- /dev/null +++ b/src/userscript.cpp @@ -0,0 +1,164 @@ +#include "userscript.h" +#include "log.h" +#include "mainwindow.h" + +#include +#include +#include + +#ifdef KXMLGUI +#include +#endif + +UserScript::UserScript() : QObject() { + setupHelpMenu(); + setupShortcutsDialog(); + setupStreamDialog(); + setupVirtmic(); +} + +void UserScript::setupHelpMenu() { +#ifdef KXMLGUI + m_kxmlgui = 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(MainWindow::instance(), aboutData); +#endif +} + +void UserScript::setupShortcutsDialog() { +#ifdef KXMLGUI +#ifdef KGLOBALACCEL + m_kglobalaccel = true; + + auto toggleMuteAction = new QAction(this); + toggleMuteAction->setText("Toggle Mute"); + toggleMuteAction->setIcon(QIcon::fromTheme("microphone-sensitivity-muted")); + connect(toggleMuteAction, &QAction::triggered, this, + &UserScript::muteToggled); + + auto toggleDeafenAction = new QAction(this); + toggleDeafenAction->setText("Toggle Deafen"); + toggleDeafenAction->setIcon(QIcon::fromTheme("audio-volume-muted")); + connect(toggleMuteAction, &QAction::triggered, this, + &UserScript::deafenToggled); + + 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 +} + +void UserScript::setupStreamDialog() { + connect(&m_streamDialog, &StreamDialog::requestedStreamStart, this, + &UserScript::startStream); +} + +void UserScript::setupVirtmic() { + m_virtmicProcess.setProcessChannelMode(QProcess::ForwardedChannels); +} + +bool UserScript::isVirtmicRunning() { + return m_virtmicProcess.state() != QProcess::NotRunning; +} + +QString UserScript::version() { return QApplication::applicationVersion(); } + +QVariant UserScript::getPref(QString name, QVariant fallback) { + return MainWindow::instance()->settings()->value(name, fallback); +} + +bool UserScript::getBoolPref(QString name, bool fallback) { + return getPref(name, fallback).toBool(); +} + +void UserScript::setPref(QString name, QVariant value) { + return MainWindow::instance()->settings()->setValue(name, value); +} + +void UserScript::setTrayIcon(bool value) { + setPref("trayIcon", value); + MainWindow::instance()->setTrayIcon(value); +} + +void UserScript::log(QString message) { + qDebug(userscriptLog) << message.toUtf8().constData(); +} + +void UserScript::showShortcutsDialog() { +#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 +} + +void UserScript::showHelpMenu() { +#ifdef KXMLGUI + m_helpMenu->aboutApplication(); +#endif +} + +void UserScript::stopVirtmic() { + if (m_virtmicProcess.state() == QProcess::Running) { + qDebug(virtmicLog) << "Stopping Virtmic"; + m_virtmicProcess.kill(); + m_virtmicProcess.waitForFinished(); + } +} + +void UserScript::startVirtmic(QString target) { + qDebug(virtmicLog) << "Starting Virtmic with target" << target; + m_virtmicProcess.start(QApplication::arguments()[0], {"--virtmic", target}); +} + +void UserScript::startStream(bool video, bool audio, int width, int height, + int frameRate, QString target) { + stopVirtmic(); + startVirtmic(audio ? target : "[None]"); + // Wait a bit for the virtmic to start + QTimer::singleShot( + 200, [=]() { emit streamStarted(video, width, height, frameRate); }); +} + +void UserScript::showStreamDialog() { + if (m_streamDialog.isHidden()) + m_streamDialog.setHidden(false); + else + m_streamDialog.activateWindow(); + m_streamDialog.updateTargets(); +} \ No newline at end of file diff --git a/src/userscript.h b/src/userscript.h new file mode 100644 index 0000000..bb1ca5e --- /dev/null +++ b/src/userscript.h @@ -0,0 +1,69 @@ +#pragma once + +#include "streamdialog.h" + +#include +#include + +#ifdef KXMLGUI +#include +#include +#include +#include +#include + +#ifdef KGLOBALACCEL +#include +#endif + +#endif + +class UserScript : public QObject { + Q_OBJECT + +public: + UserScript(); + bool isVirtmicRunning(); + Q_PROPERTY(QString version READ version); + Q_PROPERTY(bool kxmlgui MEMBER m_kxmlgui); + Q_PROPERTY(bool kglobalaccel MEMBER m_kglobalaccel); + +private: + QProcess m_virtmicProcess; + StreamDialog m_streamDialog; + bool m_kxmlgui = false; + bool m_kglobalaccel = false; +#ifdef KXMLGUI + KHelpMenu *m_helpMenu; +#ifdef KGLOBALACCEL + KActionCollection *m_actionCollection; + KShortcutsDialog *m_shortcutsDialog; +#endif +#endif + void setupHelpMenu(); + void setupShortcutsDialog(); + void setupStreamDialog(); + void setupVirtmic(); + +Q_SIGNALS: + void muteToggled(); + void deafenToggled(); + void streamStarted(bool video, int width, int height, int frameRate); + +public Q_SLOTS: + void log(QString message); + QString version(); + QVariant getPref(QString name, QVariant fallback); + bool getBoolPref(QString name, bool fallback); + void setPref(QString name, QVariant value); + void setTrayIcon(bool value); + void showShortcutsDialog(); + void showHelpMenu(); + void showStreamDialog(); + void stopVirtmic(); + void startVirtmic(QString target); + +private Q_SLOTS: + void startStream(bool video, bool audio, int width, int height, int frameRate, + QString target); +};