Compare commits
	
		
			9 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | e3d87e8202 | ||
|  | 700d576ff8 | ||
|  | 906deee580 | ||
|  | 9d9e57df1e | ||
|  | 9eae8bbe4f | ||
|  | 0493a76117 | ||
|  | 100f9bf58e | ||
|  | 1eda9d75b0 | ||
|  | f750b76068 | 
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1,2 +1,3 @@ | ||||
| /build | ||||
| .vscode | ||||
| /submodules/Vencord | ||||
|   | ||||
| @@ -44,7 +44,6 @@ set(discord-screenaudio_SRC | ||||
|   src/streamdialog.cpp | ||||
|   src/log.cpp | ||||
|   src/userscript.cpp | ||||
|   src/centralwidget.cpp | ||||
|   resources.qrc | ||||
| ) | ||||
|  | ||||
|   | ||||
							
								
								
									
										10
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								README.md
									
									
									
									
									
								
							| @@ -9,7 +9,7 @@ of [@edisionnano](https://github.com/edisionnano) and the | ||||
|  | ||||
| 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-does-this-work)). | ||||
| ([see explanation](#how-it-works)). | ||||
|  | ||||
|  | ||||
|  | ||||
| @@ -106,11 +106,9 @@ allowing access to "All system files" under the "Filesystem" section. | ||||
| ### Is there any way to add custom CSS / a theme? | ||||
|  | ||||
| Yes, you can add all your styles into | ||||
| `~/.config/discord-screenaudio/userstyles.css` (or | ||||
| `~/.var/app/de.shorsh.discord-screenaudio/config/discord-screenaudio/userstyles.css` | ||||
| if you are using the Flatpak). But please note that due to QtWebEngine | ||||
| limitations concerning content security policies, you can't use any external | ||||
| files (like `@import` or `url()`). | ||||
| `~/.config/discord-screenaudio/userstyles.css`. But please note that due to | ||||
| QtWebEngine limitations concerning content security policies, you can't use any | ||||
| external files (like `@import` or `url()`). | ||||
|  | ||||
| ## License | ||||
|  | ||||
|   | ||||
| @@ -1,65 +0,0 @@ | ||||
| setTimeout(e => { | ||||
|     let Dispatcher, lookupAsset, lookupApp, apps = {}; | ||||
|  | ||||
|     const ws = new WebSocket('ws://127.0.0.1:1337'); // connect to arRPC bridge websocket | ||||
|     ws.onmessage = async x => { | ||||
|         msg = JSON.parse(x.data); | ||||
|         console.log(msg); | ||||
|  | ||||
|         if (!Dispatcher) { | ||||
|             const wpRequire = window.webpackChunkdiscord_app.push([[Symbol()], {}, x => x]); | ||||
|             const cache = wpRequire.c; | ||||
|             window.webpackChunkdiscord_app.pop(); | ||||
|  | ||||
|             for (const id in cache) { | ||||
|                 let mod = cache[id].exports; | ||||
|                 mod = mod && (mod.Z ?? mod.ZP); | ||||
|  | ||||
|                 if (mod && mod.register && mod.wait) { | ||||
|                     Dispatcher = mod; | ||||
|                     break; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             const factories = wpRequire.m; | ||||
|             for (const id in factories) { | ||||
|                 if (factories[id].toString().includes('getAssetImage: size must === [number, number] for Twitch')) { | ||||
|                     const mod = wpRequire(id); | ||||
|  | ||||
|                     const _lookupAsset = Object.values(mod).find(e => typeof e === "function" && e.toString().includes("apply(")); | ||||
|                     lookupAsset = async (appId, name) => (await _lookupAsset(appId, [name, undefined]))[0]; | ||||
|  | ||||
|                     break; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             for (const id in factories) { | ||||
|                 if (factories[id].toString().includes(`e.application={`)) { | ||||
|                     const mod = wpRequire(id); | ||||
|  | ||||
|                     const _lookupApp = Object.values(mod).find(e => typeof e === "function" && e.toString().includes(`e.application={`)); | ||||
|                     lookupApp = async appId => { | ||||
|                         let socket = {}; | ||||
|                         await _lookupApp(socket, appId); | ||||
|                         return socket.application; | ||||
|                     }; | ||||
|  | ||||
|                     break; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if (msg.activity?.assets?.large_image) msg.activity.assets.large_image = await lookupAsset(msg.activity.application_id, msg.activity.assets.large_image); | ||||
|         if (msg.activity?.assets?.small_image) msg.activity.assets.small_image = await lookupAsset(msg.activity.application_id, msg.activity.assets.small_image); | ||||
|  | ||||
|         if (msg.activity) { | ||||
|             const appId = msg.activity.application_id; | ||||
|             if (!apps[appId]) apps[appId] = await lookupApp(appId); | ||||
|  | ||||
|             const app = apps[appId]; | ||||
|             if (!msg.activity.name) msg.activity.name = app.name; | ||||
|         } | ||||
|  | ||||
|         Dispatcher.dispatch({ type: "LOCAL_ACTIVITY_UPDATE", ...msg }); // set RPC status | ||||
|     }; | ||||
| }, 10000) | ||||
| @@ -139,12 +139,10 @@ setTimeout(() => { | ||||
|  | ||||
| function main() { | ||||
|   userscript.muteToggled.connect(() => { | ||||
|     console.log("Toggling mute"); | ||||
|     muteBtn && muteBtn.click(); | ||||
|   }); | ||||
|  | ||||
|   userscript.deafenToggled.connect(() => { | ||||
|     console.log("Toggling deafen"); | ||||
|     deafenBtn && deafenBtn.click(); | ||||
|   }); | ||||
|  | ||||
| @@ -159,25 +157,6 @@ function main() { | ||||
|     streamStartBtnClone.remove(); | ||||
|   }); | ||||
|  | ||||
|   function updateUserstyles() { | ||||
|     userscript.log("Loading userstyles..."); | ||||
|     userscript.loadingMessage = "Loading userstyles..."; | ||||
|     let stylesheet = document.getElementById("discordScreenaudioUserstyles"); | ||||
|     if (stylesheet) { | ||||
|       userscript.log("Removing old userstyles..."); | ||||
|       stylesheet.remove(); | ||||
|     } | ||||
|     stylesheet = document.createElement("style"); | ||||
|     stylesheet.id = "discordScreenaudioUserstyles"; | ||||
|     stylesheet.innerText = userscript.userstyles; | ||||
|     document.head.appendChild(stylesheet); | ||||
|     userscript.log("Finished loading userstyles"); | ||||
|     userscript.loadingMessage = ""; | ||||
|   } | ||||
|  | ||||
|   userscript.userstylesChanged.connect(updateUserstyles); | ||||
|   setTimeout(() => updateUserstyles()); | ||||
|  | ||||
|   setInterval(async () => { | ||||
|     const streamActive = | ||||
|       document.getElementsByClassName("panel-2ZFCRb activityPanel-9icbyU") | ||||
| @@ -287,11 +266,15 @@ function main() { | ||||
|     } | ||||
|  | ||||
|     muteBtn = buttonContainer | ||||
|       ? buttonContainer.getElementsByTagName("button")[0] | ||||
|       ? buttonContainer.getElementsByClassName( | ||||
|           "button-12Fmur enabled-9OeuTA button-f2h6uQ lookBlank-21BCro colorBrand-I6CyqQ grow-2sR_-F" | ||||
|         )[0] | ||||
|       : null; | ||||
|  | ||||
|     deafenBtn = buttonContainer | ||||
|       ? buttonContainer.getElementsByTagName("button")[1] | ||||
|       ? buttonContainer.getElementsByClassName( | ||||
|           "button-12Fmur enabled-9OeuTA button-f2h6uQ lookBlank-21BCro colorBrand-I6CyqQ grow-2sR_-F" | ||||
|         )[1] | ||||
|       : null; | ||||
|  | ||||
|     if (resolutionString) { | ||||
| @@ -328,18 +311,6 @@ function main() { | ||||
|             }) | ||||
|           ); | ||||
|  | ||||
|           // section.appendChild( | ||||
|           //   createButton("Install Theme", () => { | ||||
|           //     userscript.showThemeDialog(); | ||||
|           //   }) | ||||
|           // ); | ||||
|  | ||||
|           // section.appendChild( | ||||
|           //   createButton("Uninstall Theme", () => { | ||||
|           //     userscript.installUserStyles(""); | ||||
|           //   }) | ||||
|           // ); | ||||
|  | ||||
|           section.appendChild( | ||||
|             createSwitch( | ||||
|               "Move discord-screenaudio to the system tray instead of closing", | ||||
|   | ||||
							
								
								
									
										43
									
								
								assets/vencord/VencordNativeStub.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								assets/vencord/VencordNativeStub.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,43 @@ | ||||
| let webclass; | ||||
|  | ||||
| const promise = new Promise((resolve) => { | ||||
|   setTimeout(() => { | ||||
|     new QWebChannel(qt.webChannelTransport, function (channel) { | ||||
|       webclass = channel.objects.webclass; | ||||
|       resolve(); | ||||
|     }); | ||||
|   }); | ||||
| }); | ||||
|  | ||||
| async function prepareWebclass() { | ||||
|   if (!webclass) await promise; | ||||
| } | ||||
|  | ||||
| window.VencordNative = { | ||||
|   getVersions: () => ({}), | ||||
|   ipc: { | ||||
|     send: async (event: string, ...args: any[]) => { | ||||
|       await prepareWebclass(); | ||||
|       webclass.vencordSend(event, args); | ||||
|     }, | ||||
|     sendSync: (event: string, ...args: any[]) => { | ||||
|       // We need this hack because Vencord requires its settings right when it starts | ||||
|       if (event === "VencordGetSettings") { | ||||
|         return window.discordScreenaudioVencordSettings || "{}"; | ||||
|       } else throw new Error("Synchroneous IPC not implemented"); | ||||
|     }, | ||||
|     on(event: string, listener: () => {}) { | ||||
|       // TODO quickCss | ||||
|     }, | ||||
|     off(event: string, listener: () => {}) { | ||||
|       // not used for now | ||||
|     }, | ||||
|     invoke: async (event: string, ...args: any[]) => { | ||||
|       await prepareWebclass(); | ||||
|       if (event === "VencordSetSettings") { | ||||
|         window.discordScreenaudioVencordSettings = args[0]; | ||||
|       } | ||||
|       return webclass.vencordSend(event, args); | ||||
|     }, | ||||
|   }, | ||||
| }; | ||||
							
								
								
									
										14
									
								
								assets/vencord/plugin.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								assets/vencord/plugin.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| import definePlugin from "../utils/types"; | ||||
|  | ||||
| export default definePlugin({ | ||||
|   name: "discord-screenaudio", | ||||
|   authors: [ | ||||
|     { | ||||
|       name: "maltejur", | ||||
|       id: 205966226709676032n, | ||||
|     }, | ||||
|   ], | ||||
|   required: true, | ||||
|   description: "UI patches for discord-screenaudio.", | ||||
|   patches: [], | ||||
| }); | ||||
							
								
								
									
										15
									
								
								assets/vencord/settings.patch
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								assets/vencord/settings.patch
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | ||||
| --- a/src/components/VencordSettings/VencordTab.tsx | ||||
| +++ b/src/components/VencordSettings/VencordTab.tsx | ||||
| @@ -87,10 +87,10 @@ function VencordSettings() { | ||||
|                  <Card className={cl("quick-actions-card")}> | ||||
|                      {IS_WEB ? ( | ||||
|                          <Button | ||||
| -                            onClick={() => require("../Monaco").launchMonacoEditor()} | ||||
| +                            onClick={() => VencordNative.ipc.send(IpcEvents.OPEN_EXTERNAL, settingsDir)} | ||||
|                              size={Button.Sizes.SMALL} | ||||
|                              disabled={settingsDir === "Loading..."}> | ||||
| -                            Open QuickCSS File | ||||
| +                            Launch Directory | ||||
|                          </Button> | ||||
|                      ) : ( | ||||
|                          <React.Fragment> | ||||
							
								
								
									
										410
									
								
								assets/vencord/vencord.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										410
									
								
								assets/vencord/vencord.js
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| @@ -2,7 +2,7 @@ | ||||
| <RCC> | ||||
|   <qresource> | ||||
|     <file>assets/userscript.js</file> | ||||
|     <file>assets/bridge_mod.js</file> | ||||
|     <file>assets/vencord/vencord.js</file> | ||||
|     <file>assets/de.shorsh.discord-screenaudio.png</file> | ||||
|   </qresource> | ||||
| </RCC> | ||||
							
								
								
									
										39
									
								
								scripts/build_vencord.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										39
									
								
								scripts/build_vencord.sh
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,39 @@ | ||||
| #!/usr/bin/bash | ||||
| set -e | ||||
|  | ||||
| cd "$(dirname "$0")/../submodules" | ||||
|  | ||||
| echo_status() { | ||||
|   echo | ||||
|   echo | ||||
|   echo "-> $1..." | ||||
| } | ||||
|  | ||||
| if [ ! -d "Vencord" ]; then | ||||
|   echo_status "Cloning Vencord" | ||||
|   git clone https://github.com/Vendicated/Vencord.git | ||||
|   cd Vencord | ||||
| else | ||||
|   echo_status "Fetching Vencord changes" | ||||
|   cd Vencord | ||||
|   git fetch | ||||
| fi | ||||
|  | ||||
| echo_status "Checking out latest commit" | ||||
| git reset --hard HEAD | ||||
| git checkout main | ||||
| git reset --hard devbuild | ||||
|  | ||||
| echo_status "Installing dependencies" | ||||
| pnpm i | ||||
|  | ||||
| echo_status "Patching Vencord" | ||||
| cp -v ../../assets/vencord/plugin.js ./src/plugins/discord-screenaudio.js | ||||
| cp -v ../../assets/vencord/VencordNativeStub.ts ./browser/VencordNativeStub.ts | ||||
| patch -p1 -i ../../assets/vencord/settings.patch | ||||
|  | ||||
| echo_status "Building Vencord" | ||||
| pnpm run buildWeb | ||||
|  | ||||
| echo_status "Copying built file" | ||||
| cp -v ./dist/browser.js ../../assets/vencord/vencord.js | ||||
| @@ -1,84 +0,0 @@ | ||||
| #include "centralwidget.h" | ||||
| #include "discordpage.h" | ||||
| #include "mainwindow.h" | ||||
|  | ||||
| #include <QWebEngineNotification> | ||||
| #include <QWebEngineProfile> | ||||
| #include <QWebEngineScript> | ||||
| #include <QWebEngineScriptCollection> | ||||
| #include <QWebEngineSettings> | ||||
|  | ||||
| CentralWidget::CentralWidget(QWidget *parent) : QWidget(parent) { | ||||
|   setStyleSheet("background-color:#313338;"); | ||||
|   m_layout = new QVBoxLayout(this); | ||||
|   m_layout->setMargin(0); | ||||
|   m_layout->setSpacing(0); | ||||
|   setupWebView(); | ||||
| } | ||||
|  | ||||
| void CentralWidget::setupWebView() { | ||||
|   auto page = new DiscordPage(this); | ||||
|  | ||||
|   m_webView = new QWebEngineView(this); | ||||
|   m_webView->setPage(page); | ||||
|  | ||||
|   bool useNotifySend = MainWindow::instance() | ||||
|                            ->settings() | ||||
|                            ->value("useNotifySend", false) | ||||
|                            .toBool(); | ||||
|   if (m_useKF5Notifications || useNotifySend) | ||||
|     QWebEngineProfile::defaultProfile()->setNotificationPresenter( | ||||
|         [&](std::unique_ptr<QWebEngineNotification> notificationInfo) { | ||||
|           if (useNotifySend) { | ||||
|             auto title = notificationInfo->title(); | ||||
|             auto message = notificationInfo->message(); | ||||
|             auto image_path = | ||||
|                 QString("/tmp/discord-screenaudio-%1.png").arg(title); | ||||
|             notificationInfo->icon().save(image_path); | ||||
|             QProcess::execute("notify-send", | ||||
|                               {"--icon", image_path, "--app-name", | ||||
|                                "discord-screenaudio", title, message}); | ||||
|           } else if (m_useKF5Notifications) { | ||||
| #ifdef KNOTIFICATIONS | ||||
|             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(); | ||||
|                       show(); | ||||
|                       activateWindow(); | ||||
|                     }); | ||||
|             notification->sendEvent(); | ||||
| #endif | ||||
|           } | ||||
|         }); | ||||
|  | ||||
|   connect(page->userScript(), &UserScript::loadingMessageChanged, this, | ||||
|           &CentralWidget::setLoadingIndicator); | ||||
|  | ||||
|   m_layout->addWidget(m_webView); | ||||
| } | ||||
|  | ||||
| void CentralWidget::setLoadingIndicator(QString text) { | ||||
|   if (text != "") { | ||||
|     if (m_loadingLabel == nullptr) { | ||||
|       m_loadingLabel = new QLabel(this); | ||||
|       m_loadingLabel->setMaximumHeight(20); | ||||
|       m_loadingLabel->setAlignment(Qt::AlignHCenter); | ||||
|       m_loadingLabel->setStyleSheet("color:#dedede;"); | ||||
|       m_layout->addWidget(m_loadingLabel); | ||||
|     } | ||||
|     m_loadingLabel->setText(text.mid(0, 100)); | ||||
|   } else { | ||||
|     if (m_loadingLabel != nullptr) { | ||||
|       m_layout->removeWidget(m_loadingLabel); | ||||
|       m_loadingLabel->deleteLater(); | ||||
|       m_loadingLabel = nullptr; | ||||
|     } | ||||
|   } | ||||
| } | ||||
| @@ -1,29 +0,0 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include <QLabel> | ||||
| #include <QVBoxLayout> | ||||
| #include <QWebEnginePage> | ||||
| #include <QWebEngineProfile> | ||||
| #include <QWebEngineView> | ||||
| #include <QWidget> | ||||
|  | ||||
| class CentralWidget : public QWidget { | ||||
|   Q_OBJECT | ||||
|  | ||||
| public: | ||||
|   CentralWidget(QWidget *parent = nullptr); | ||||
|  | ||||
| private: | ||||
|   void setupWebView(); | ||||
|   QVBoxLayout *m_layout; | ||||
|   QWebEngineView *m_webView; | ||||
| #ifdef KNOTIFICATIONS | ||||
|   bool m_useKF5Notifications = true; | ||||
| #else | ||||
|   bool m_useKF5Notifications = false; | ||||
| #endif | ||||
|   QLabel *m_loadingLabel = nullptr; | ||||
|  | ||||
| public Q_SLOTS: | ||||
|   void setLoadingIndicator(QString text); | ||||
| }; | ||||
| @@ -16,12 +16,10 @@ | ||||
| #include <QWebEngineSettings> | ||||
|  | ||||
| DiscordPage::DiscordPage(QWidget *parent) : QWebEnginePage(parent) { | ||||
|   setBackgroundColor(QColor("#313338")); | ||||
|   setBackgroundColor(QColor("#202225")); | ||||
|  | ||||
|   connect(this, &QWebEnginePage::featurePermissionRequested, this, | ||||
|           &DiscordPage::featurePermissionRequested); | ||||
|   connect(this, &DiscordPage::fullScreenRequested, MainWindow::instance(), | ||||
|           &MainWindow::fullScreenRequested); | ||||
|  | ||||
|   setupPermissions(); | ||||
|  | ||||
| @@ -35,9 +33,19 @@ DiscordPage::DiscordPage(QWidget *parent) : QWebEnginePage(parent) { | ||||
|  | ||||
|   injectFile(&DiscordPage::injectScript, "userscript.js", | ||||
|              ":/assets/userscript.js"); | ||||
|               | ||||
|   injectFile(&DiscordPage::injectScript, "bridge_mod.js", | ||||
|              ":/assets/bridge_mod.js"); | ||||
|  | ||||
|   injectFile(&DiscordPage::injectScript, "userscript.js", | ||||
|              ":/assets/userscript.js"); | ||||
|   QFile vencord(":/assets/vencord/vencord.js"); | ||||
|   if (!vencord.open(QIODevice::ReadOnly)) | ||||
|     qFatal("Failed to load vencord source with error: %s", | ||||
|            vencord.errorString().toLatin1().constData()); | ||||
|   injectScript( | ||||
|       "vencord.js", | ||||
|       QString("window.discordScreenaudioVencordSettings = `%1`; %2") | ||||
|           .arg(m_userScript.vencordSend("VencordGetSettings", {}).toString(), | ||||
|                vencord.readAll())); | ||||
|   vencord.close(); | ||||
|  | ||||
|   setupUserStyles(); | ||||
| } | ||||
| @@ -57,79 +65,13 @@ void DiscordPage::setupPermissions() { | ||||
| } | ||||
|  | ||||
| void DiscordPage::setupUserStyles() { | ||||
|   qDebug(userstylesLog).noquote() | ||||
|       << "Looking for userstyles in" << m_configLocation.absolutePath(); | ||||
|   m_userStylesFile = | ||||
|       new QFile(m_configLocation.absoluteFilePath("userstyles.css")); | ||||
|   if (m_userStylesFile->exists()) { | ||||
|     qDebug(userstylesLog).noquote() | ||||
|         << "Found userstyles:" << m_userStylesFile->fileName(); | ||||
|     m_userStylesFile->open(QIODevice::ReadOnly); | ||||
|     m_userStylesContent = m_userStylesFile->readAll(); | ||||
|     m_userStylesFile->close(); | ||||
|     fetchUserStyles(); | ||||
|   QString file = | ||||
|       QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation) + | ||||
|       "/userstyles.css"; | ||||
|   if (QFileInfo(file).exists()) { | ||||
|     qDebug(mainLog) << "Found userstyles:" << file; | ||||
|     injectFile(&DiscordPage::injectStylesheet, "userstyles.js", file); | ||||
|   } | ||||
|   connect(&m_userScript, &UserScript::shouldInstallUserStyles, this, | ||||
|           &DiscordPage::getUserStyles); | ||||
| } | ||||
|  | ||||
| const QRegularExpression importRegex( | ||||
|     R"r(@import (?:url\(|)['"]{0,1}(?!.*usrbgs?\.css)([^'"]+?)['"]{0,1}(?:|\));)r"); | ||||
| const QRegularExpression urlRegex( | ||||
|     R"r(url\(['"]{0,1}((?!https:\/\/fonts.gstatic.com)(?!data:)(?!.*\.woff2)(?!.*\.ttf)[^'"]+?)['"]{0,1}\))r"); | ||||
|  | ||||
| void DiscordPage::fetchUserStyles() { | ||||
|   m_userScript.setProperty( | ||||
|       "loadingMessage", "Loading userstyles: Fetching additional resources..."); | ||||
|   bool foundImport = true; | ||||
|   auto match = importRegex.match(m_userStylesContent); | ||||
|   if (!match.hasMatch()) { | ||||
|     foundImport = false; | ||||
|     match = urlRegex.match(m_userStylesContent); | ||||
|   } | ||||
|   if (match.hasMatch()) { | ||||
|     auto url = match.captured(1); | ||||
|     qDebug(userstylesLog) << "Fetching" << url; | ||||
|     m_userScript.setProperty( | ||||
|         "loadingMessage", | ||||
|         QString("Loading userstyles: Fetching %1...").arg(url)); | ||||
|     QNetworkRequest request(url); | ||||
|     auto reply = m_networkAccessManager.get(request); | ||||
|     connect(reply, &QNetworkReply::finished, [=]() { | ||||
|       QByteArray content = ""; | ||||
|       if (reply->error() == QNetworkReply::NoError) { | ||||
|         if (!reply->attribute(QNetworkRequest::RedirectionTargetAttribute) | ||||
|                  .isNull()) | ||||
|           content = | ||||
|               reply->attribute(QNetworkRequest::RedirectionTargetAttribute) | ||||
|                   .toByteArray(); | ||||
|         else | ||||
|           content = reply->readAll(); | ||||
|       } else | ||||
|         qDebug(userstylesLog) << reply->errorString().toUtf8().constData(); | ||||
|       reply->deleteLater(); | ||||
|       m_userStylesContent = m_userStylesContent.replace( | ||||
|           match.captured(0), foundImport | ||||
|                                  ? content | ||||
|                                  : "url(data:application/octet-stream;base64," + | ||||
|                                        content.toBase64() + ")"); | ||||
|       fetchUserStyles(); | ||||
|     }); | ||||
|     return; | ||||
|   } | ||||
|   qDebug(userstylesLog) << "Injecting userstyles"; | ||||
|   m_userScript.setProperty("userstyles", m_userStylesContent); | ||||
|   m_userScript.setProperty("loadingMessage", ""); | ||||
|   if (!m_configLocation.exists()) | ||||
|     m_configLocation.mkpath("."); | ||||
|   m_userStylesFile->open(QIODevice::WriteOnly); | ||||
|   m_userStylesFile->write(m_userStylesContent.toUtf8()); | ||||
|   m_userStylesFile->close(); | ||||
| } | ||||
|  | ||||
| void DiscordPage::getUserStyles(QString url) { | ||||
|   m_userStylesContent = url == "" ? "" : QString("@import url(%1);").arg(url); | ||||
|   fetchUserStyles(); | ||||
| } | ||||
|  | ||||
| void DiscordPage::injectScript( | ||||
| @@ -154,6 +96,7 @@ void DiscordPage::injectScript(QString name, QString content) { | ||||
|  | ||||
| void DiscordPage::injectStylesheet(QString name, QString content) { | ||||
|   auto script = QString(R"(const stylesheet = document.createElement("style"); | ||||
| stylesheet.type = "text/css"; | ||||
| stylesheet.id = "%1"; | ||||
| stylesheet.innerText = `%2`; | ||||
| document.head.appendChild(stylesheet); | ||||
| @@ -262,15 +205,11 @@ void DiscordPage::javaScriptConsoleMessage( | ||||
|     QWebEnginePage::JavaScriptConsoleMessageLevel level, const QString &message, | ||||
|     int lineNumber, const QString &sourceID) { | ||||
|   auto colorSegments = message.split("%c"); | ||||
|   if (colorSegments[0] != "") { | ||||
|     for (auto line : colorSegments[0].split("\n")) | ||||
|       qDebug(discordLog) << line.toUtf8().constData(); | ||||
|   } | ||||
|   for (auto segment : colorSegments.mid(1)) { | ||||
|     auto lines = segment.split("\n"); | ||||
|     QString ansi; | ||||
|     uint endOfStyles = lines.length(); | ||||
|     for (auto line = 1; line < lines.length(); line++) { | ||||
|     for (size_t line = 1; line < lines.length(); line++) { | ||||
|       if (!lines[line].endsWith(";")) { | ||||
|         endOfStyles = line; | ||||
|         break; | ||||
| @@ -284,7 +223,9 @@ void DiscordPage::javaScriptConsoleMessage( | ||||
|       } | ||||
|     } | ||||
|     qDebug(discordLog) << (ansi + lines[0].trimmed() + "\033[0m " + | ||||
|                            lines[endOfStyles].trimmed()) | ||||
|                            ((lines.length() > endOfStyles) | ||||
|                                 ? lines[endOfStyles].trimmed() | ||||
|                                 : "")) | ||||
|                               .toUtf8() | ||||
|                               .constData(); | ||||
|     for (auto line : lines.mid(endOfStyles + 1)) { | ||||
| @@ -292,5 +233,3 @@ void DiscordPage::javaScriptConsoleMessage( | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| UserScript *DiscordPage::userScript() { return &m_userScript; } | ||||
|   | ||||
| @@ -1,11 +1,9 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "streamdialog.h" | ||||
| #include "userscript.h" | ||||
| #include "virtmic.h" | ||||
|  | ||||
| #include <QDir> | ||||
| #include <QFile> | ||||
| #include <QNetworkAccessManager> | ||||
| #include <QStandardPaths> | ||||
| #include <QWebEngineFullScreenRequest> | ||||
| #include <QWebEnginePage> | ||||
| #include <QWebEngineScript> | ||||
| @@ -15,18 +13,11 @@ class DiscordPage : public QWebEnginePage { | ||||
|  | ||||
| public: | ||||
|   explicit DiscordPage(QWidget *parent = nullptr); | ||||
|   UserScript *userScript(); | ||||
|  | ||||
| private: | ||||
|   UserScript m_userScript; | ||||
|   QFile *m_userStylesFile; | ||||
|   QString m_userStylesContent; | ||||
|   QNetworkAccessManager m_networkAccessManager; | ||||
|   const QDir m_configLocation = | ||||
|       QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation); | ||||
|   void setupPermissions(); | ||||
|   void setupUserStyles(); | ||||
|   void fetchUserStyles(); | ||||
|   bool acceptNavigationRequest(const QUrl &url, | ||||
|                                QWebEnginePage::NavigationType type, | ||||
|                                bool isMainFrame) override; | ||||
| @@ -45,9 +36,6 @@ private: | ||||
| private Q_SLOTS: | ||||
|   void featurePermissionRequested(const QUrl &securityOrigin, | ||||
|                                   QWebEnginePage::Feature feature); | ||||
|  | ||||
| public Q_SLOTS: | ||||
|   void getUserStyles(QString url); | ||||
| }; | ||||
|  | ||||
| // Will immediately get destroyed again but is needed for navigation to | ||||
|   | ||||
| @@ -5,4 +5,3 @@ Q_LOGGING_CATEGORY(discordLog, "discord"); | ||||
| Q_LOGGING_CATEGORY(userscriptLog, "userscript"); | ||||
| Q_LOGGING_CATEGORY(virtmicLog, "virtmic"); | ||||
| Q_LOGGING_CATEGORY(shortcutLog, "shortcut"); | ||||
| Q_LOGGING_CATEGORY(userstylesLog, "userstyles"); | ||||
|   | ||||
| @@ -7,4 +7,3 @@ Q_DECLARE_LOGGING_CATEGORY(discordLog); | ||||
| Q_DECLARE_LOGGING_CATEGORY(userscriptLog); | ||||
| Q_DECLARE_LOGGING_CATEGORY(virtmicLog); | ||||
| Q_DECLARE_LOGGING_CATEGORY(shortcutLog); | ||||
| Q_DECLARE_LOGGING_CATEGORY(userstylesLog); | ||||
|   | ||||
| @@ -16,7 +16,11 @@ | ||||
| #include <QThread> | ||||
| #include <QTimer> | ||||
| #include <QUrl> | ||||
| #include <QWebEngineFullScreenRequest> | ||||
| #include <QWebEngineNotification> | ||||
| #include <QWebEngineProfile> | ||||
| #include <QWebEngineScript> | ||||
| #include <QWebEngineScriptCollection> | ||||
| #include <QWebEngineSettings> | ||||
| #include <QWidget> | ||||
|  | ||||
| MainWindow *MainWindow::m_instance = nullptr; | ||||
| @@ -25,17 +29,12 @@ MainWindow::MainWindow(bool useNotifySend, QWidget *parent) | ||||
|     : QMainWindow(parent) { | ||||
|   assert(MainWindow::m_instance == nullptr); | ||||
|   MainWindow::m_instance = this; | ||||
|   m_useNotifySend = useNotifySend; | ||||
|   setupSettings(); | ||||
|   m_settings->setValue("useNotifySend", useNotifySend); | ||||
|   m_centralWidget = new CentralWidget(this); | ||||
|   setCentralWidget(m_centralWidget); | ||||
|   setupWebView(); | ||||
|   setupTrayIcon(); | ||||
|   if (m_settings->contains("geometry")) { | ||||
|     restoreGeometry(m_settings->value("geometry").toByteArray()); | ||||
|   } else { | ||||
|     resize(1000, 700); | ||||
|     showMaximized(); | ||||
|   } | ||||
|   resize(1000, 700); | ||||
|   showMaximized(); | ||||
|   if (m_settings->value("trayIcon", false).toBool() && | ||||
|       m_settings->value("startHidden", false).toBool()) { | ||||
|     hide(); | ||||
| @@ -43,6 +42,49 @@ MainWindow::MainWindow(bool useNotifySend, QWidget *parent) | ||||
|   } | ||||
| } | ||||
|  | ||||
| void MainWindow::setupWebView() { | ||||
|   auto page = new DiscordPage(this); | ||||
|   connect(page, &QWebEnginePage::fullScreenRequested, this, | ||||
|           &MainWindow::fullScreenRequested); | ||||
|  | ||||
|   m_webView = new QWebEngineView(this); | ||||
|   m_webView->setPage(page); | ||||
|  | ||||
|   if (m_useKF5Notifications || m_useNotifySend) | ||||
|     QWebEngineProfile::defaultProfile()->setNotificationPresenter( | ||||
|         [&](std::unique_ptr<QWebEngineNotification> notificationInfo) { | ||||
|           if (m_useNotifySend) { | ||||
|             auto title = notificationInfo->title(); | ||||
|             auto message = notificationInfo->message(); | ||||
|             auto image_path = | ||||
|                 QString("/tmp/discord-screenaudio-%1.png").arg(title); | ||||
|             notificationInfo->icon().save(image_path); | ||||
|             QProcess::execute("notify-send", | ||||
|                               {"--icon", image_path, "--app-name", | ||||
|                                "discord-screenaudio", title, message}); | ||||
|           } else if (m_useKF5Notifications) { | ||||
| #ifdef KNOTIFICATIONS | ||||
|             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(); | ||||
|                       show(); | ||||
|                       activateWindow(); | ||||
|                     }); | ||||
|             notification->sendEvent(); | ||||
| #endif | ||||
|           } | ||||
|         }); | ||||
|  | ||||
|   setCentralWidget(m_webView); | ||||
| } | ||||
|  | ||||
| void MainWindow::fullScreenRequested( | ||||
|     QWebEngineFullScreenRequest fullScreenRequest) { | ||||
|   fullScreenRequest.accept(); | ||||
| @@ -118,14 +160,8 @@ void MainWindow::setTrayIcon(bool enabled) { | ||||
| void MainWindow::closeEvent(QCloseEvent *event) { | ||||
|   if (m_settings->value("trayIcon", false).toBool()) { | ||||
|     hide(); | ||||
|   } else { | ||||
|     m_settings->setValue("geometry", saveGeometry()); | ||||
|   } else | ||||
|     QApplication::quit(); | ||||
|   } | ||||
| } | ||||
|  | ||||
| MainWindow *MainWindow::instance() { return m_instance; } | ||||
|  | ||||
| CentralWidget *MainWindow::centralWidget() { | ||||
|   return instance()->m_centralWidget; | ||||
| }; | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "centralwidget.h" | ||||
| #include "discordpage.h" | ||||
|  | ||||
| #include <QMainWindow> | ||||
| #include <QMenu> | ||||
| @@ -8,8 +8,10 @@ | ||||
| #include <QSettings> | ||||
| #include <QString> | ||||
| #include <QSystemTrayIcon> | ||||
| #include <QVBoxLayout> | ||||
| #include <QVector> | ||||
| #include <QWebEnginePage> | ||||
| #include <QWebEngineProfile> | ||||
| #include <QWebEngineView> | ||||
|  | ||||
| class MainWindow : public QMainWindow { | ||||
|   Q_OBJECT | ||||
| @@ -18,22 +20,31 @@ public: | ||||
|   explicit MainWindow(bool useNotifySend = false, QWidget *parent = nullptr); | ||||
|   static MainWindow *instance(); | ||||
|   QSettings *settings() const; | ||||
|   static CentralWidget *centralWidget(); | ||||
|  | ||||
| private: | ||||
|   void setupWebView(); | ||||
|   void setupTrayIcon(); | ||||
|   void cleanTrayIcon(); | ||||
|   void setupSettings(); | ||||
|   QWebEngineView *m_webView; | ||||
|   QWebEngineProfile *prepareProfile(); | ||||
|   DiscordPage *m_discordPage; | ||||
|   void closeEvent(QCloseEvent *event) override; | ||||
|   QSystemTrayIcon *m_trayIcon = nullptr; | ||||
|   QMenu *m_trayIconMenu; | ||||
|   QSettings *m_settings; | ||||
|   bool m_wasMaximized; | ||||
|   static MainWindow *m_instance; | ||||
|   CentralWidget *m_centralWidget; | ||||
|   bool m_useNotifySend; | ||||
| #ifdef KNOTIFICATIONS | ||||
|   bool m_useKF5Notifications = true; | ||||
| #else | ||||
|   bool m_useKF5Notifications = false; | ||||
| #endif | ||||
|  | ||||
| public Q_SLOTS: | ||||
|   void setTrayIcon(bool enabled); | ||||
|  | ||||
| private Q_SLOTS: | ||||
|   void fullScreenRequested(QWebEngineFullScreenRequest fullScreenRequest); | ||||
| }; | ||||
|   | ||||
| @@ -4,9 +4,9 @@ | ||||
|  | ||||
| #include <QApplication> | ||||
| #include <QDebug> | ||||
| #include <QDesktopServices> | ||||
| #include <QDir> | ||||
| #include <QFile> | ||||
| #include <QInputDialog> | ||||
| #include <QMessageBox> | ||||
| #include <QStandardPaths> | ||||
| #include <QTimer> | ||||
|  | ||||
| @@ -62,7 +62,7 @@ void UserScript::setupShortcutsDialog() { | ||||
|   auto toggleDeafenAction = new QAction(this); | ||||
|   toggleDeafenAction->setText("Toggle Deafen"); | ||||
|   toggleDeafenAction->setIcon(QIcon::fromTheme("audio-volume-muted")); | ||||
|   connect(toggleDeafenAction, &QAction::triggered, this, | ||||
|   connect(toggleMuteAction, &QAction::triggered, this, | ||||
|           &UserScript::deafenToggled); | ||||
|  | ||||
|   m_actionCollection = new KActionCollection(this); | ||||
| @@ -168,13 +168,67 @@ void UserScript::showStreamDialog() { | ||||
|   m_streamDialog->updateTargets(); | ||||
| } | ||||
|  | ||||
| void UserScript::showThemeDialog() { | ||||
|   auto url = QInputDialog::getText(MainWindow::instance(), "Theme Installation", | ||||
|                                    "Please enter the URL of the Theme"); | ||||
|   if (url != "") | ||||
|     emit shouldInstallUserStyles(url); | ||||
| } | ||||
| QVariant UserScript::vencordSend(QString event, QVariantList args) { | ||||
|   QString configFolder = | ||||
|       QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation) + | ||||
|       "/vencord"; | ||||
|   QString quickCssFile = configFolder + "/quickCss.css"; | ||||
|   QString settingsFile = configFolder + "/settings.json"; | ||||
|  | ||||
| void UserScript::installUserStyles(QString url) { | ||||
|   emit shouldInstallUserStyles(url); | ||||
|   if (!QDir().exists(configFolder)) | ||||
|     QDir().mkpath(configFolder); | ||||
|  | ||||
|   if (event == "VencordGetRepo") { | ||||
|     return true; | ||||
|   } | ||||
|   if (event == "VencordGetSettingsDir") { | ||||
|     return configFolder; | ||||
|   } | ||||
|   if (event == "VencordGetQuickCss") { | ||||
|     if (QFile::exists(quickCssFile)) { | ||||
|       QFile file(quickCssFile); | ||||
|       if (!file.open(QIODevice::ReadOnly)) | ||||
|         qFatal("Failed to load %s with error: %s", | ||||
|                quickCssFile.toLatin1().constData(), | ||||
|                file.errorString().toLatin1().constData()); | ||||
|       auto content = file.readAll(); | ||||
|       file.close(); | ||||
|       return QString(content); | ||||
|     } else | ||||
|       return ""; | ||||
|   } | ||||
|   if (event == "VencordGetSettings") { | ||||
|     if (QFile::exists(settingsFile)) { | ||||
|       QFile file(settingsFile); | ||||
|       if (!file.open(QIODevice::ReadOnly)) | ||||
|         qFatal("Failed to load %s with error: %s", | ||||
|                settingsFile.toLatin1().constData(), | ||||
|                file.errorString().toLatin1().constData()); | ||||
|       auto content = file.readAll(); | ||||
|       file.close(); | ||||
|       return QString(content); | ||||
|     } else | ||||
|       return "{}"; | ||||
|   } | ||||
|   if (event == "VencordSetSettings") { | ||||
|     QFile file(settingsFile); | ||||
|     if (!file.open(QIODevice::WriteOnly)) | ||||
|       qFatal("Failed to load %s with error: %s", | ||||
|              settingsFile.toLatin1().constData(), | ||||
|              file.errorString().toLatin1().constData()); | ||||
|     file.write(args[0].toString().toUtf8()); | ||||
|     file.close(); | ||||
|     return true; | ||||
|   } | ||||
|   if (event == "VencordGetUpdates") { | ||||
|     return QVariantMap{{"ok", true}, {"value", QVariantList()}}; | ||||
|   } | ||||
|   if (event == "VencordOpenExternal") { | ||||
|     QDesktopServices::openUrl(QUrl(args[0].toString())); | ||||
|     return true; | ||||
|   } | ||||
|   if (event == "VencordOpenQuickCss") { | ||||
|     return true; | ||||
|   } | ||||
|   assert(false); | ||||
| } | ||||
|   | ||||
| @@ -18,10 +18,6 @@ | ||||
|  | ||||
| #endif | ||||
|  | ||||
| #ifdef KNOTIFICATIONS | ||||
| #include <KNotification> | ||||
| #endif | ||||
|  | ||||
| class UserScript : public QObject { | ||||
|   Q_OBJECT | ||||
|  | ||||
| @@ -31,17 +27,12 @@ public: | ||||
|   Q_PROPERTY(QString version READ version CONSTANT); | ||||
|   Q_PROPERTY(bool kxmlgui MEMBER m_kxmlgui CONSTANT); | ||||
|   Q_PROPERTY(bool kglobalaccel MEMBER m_kglobalaccel CONSTANT); | ||||
|   Q_PROPERTY(QString userstyles MEMBER m_userstyles NOTIFY userstylesChanged); | ||||
|   Q_PROPERTY(QString loadingMessage MEMBER m_loadingMessage NOTIFY | ||||
|                  loadingMessageChanged); | ||||
|  | ||||
| private: | ||||
|   QProcess m_virtmicProcess; | ||||
|   StreamDialog *m_streamDialog; | ||||
|   bool m_kxmlgui = false; | ||||
|   bool m_kglobalaccel = false; | ||||
|   QString m_userstyles; | ||||
|   QString m_loadingMessage; | ||||
| #ifdef KXMLGUI | ||||
|   KHelpMenu *m_helpMenu; | ||||
| #ifdef KGLOBALACCEL | ||||
| @@ -49,6 +40,7 @@ private: | ||||
|   KShortcutsDialog *m_shortcutsDialog; | ||||
| #endif | ||||
| #endif | ||||
|   QString m_vencordSettings; | ||||
|   void setupHelpMenu(); | ||||
|   void setupShortcutsDialog(); | ||||
|   void setupStreamDialog(); | ||||
| @@ -58,9 +50,6 @@ Q_SIGNALS: | ||||
|   void muteToggled(); | ||||
|   void deafenToggled(); | ||||
|   void streamStarted(bool video, int width, int height, int frameRate); | ||||
|   void userstylesChanged(); | ||||
|   void loadingMessageChanged(QString message); | ||||
|   void shouldInstallUserStyles(QString url); | ||||
|  | ||||
| public Q_SLOTS: | ||||
|   void log(QString message); | ||||
| @@ -74,8 +63,7 @@ public Q_SLOTS: | ||||
|   void showStreamDialog(); | ||||
|   void stopVirtmic(); | ||||
|   void startVirtmic(QString target); | ||||
|   void showThemeDialog(); | ||||
|   void installUserStyles(QString url); | ||||
|   QVariant vencordSend(QString event, QVariantList args); | ||||
|  | ||||
| private Q_SLOTS: | ||||
|   void startStream(bool video, bool audio, int width, int height, int frameRate, | ||||
|   | ||||
		Reference in New Issue
	
	Block a user