Compare commits
	
		
			14 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 7b6e8fc473 | ||
|  | 8f0a810539 | ||
|  | a6eb82948f | ||
|  | 24727f398a | ||
|  | c43e9953a5 | ||
|  | 08cb713e8c | ||
|  | 27faed4a3a | ||
|  | 10fff86ca4 | ||
|  | 87e84dec5c | ||
|  | 4b782133ae | ||
|  | 423884ae0c | ||
|  | 7ea3d0aab1 | ||
|  | 18b15f5cf4 | ||
|  | 3540774c82 | 
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1,2 +1,3 @@ | ||||
| /build | ||||
| .vscode | ||||
| /submodules/arrpc | ||||
|   | ||||
| @@ -62,6 +62,9 @@ You have multiple options: | ||||
| With apt: | ||||
| `apt install -y build-essential cmake qtbase5-dev qtwebengine5-dev libkf5notifications-dev libkf5xmlgui-dev libkf5globalaccel-dev pkg-config libpipewire-0.3-dev git` | ||||
|  | ||||
| With dnf: | ||||
| `dnf install @development-tools cmake qt5-qtbase-devel qt5-qtwebengine-devel kf5-knotifications-devel kf5-kxmlgui-devel kf5-kglobalaccel-devel pkgconfig pipewire-devel git` | ||||
|  | ||||
| ### Building | ||||
|  | ||||
| First, clone the repository: | ||||
|   | ||||
							
								
								
									
										1
									
								
								assets/arrpc.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								assets/arrpc.js
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										1
									
								
								assets/arrpc_bridge_mod.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								assets/arrpc_bridge_mod.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| setTimeout((i=>{let t,a,s,e={};new WebSocket("ws://127.0.0.1:1337").onmessage=async i=>{if(msg=JSON.parse(i.data),console.log(msg),!t){const i=window.webpackChunkdiscord_app.push([[Symbol()],{},i=>i]),e=i.c;window.webpackChunkdiscord_app.pop();for(const i in e){let a=e[i].exports;if(a=a&&(a.Z??a.ZP),a&&a.register&&a.wait){t=a;break}}const n=i.m;for(const t in n)if(n[t].toString().includes("getAssetImage: size must === [number, number] for Twitch")){const s=i(t),e=Object.values(s).find((i=>"function"==typeof i&&i.toString().includes("apply(")));a=async(i,t)=>(await e(i,[t,void 0]))[0];break}for(const t in n)if(n[t].toString().includes("e.application={")){const a=i(t),e=Object.values(a).find((i=>"function"==typeof i&&i.toString().includes("e.application={")));s=async i=>{let t={};return await e(t,i),t.application};break}}if(msg.activity?.assets?.large_image&&(msg.activity.assets.large_image=await a(msg.activity.application_id,msg.activity.assets.large_image)),msg.activity?.assets?.small_image&&(msg.activity.assets.small_image=await a(msg.activity.application_id,msg.activity.assets.small_image)),msg.activity){const i=msg.activity.application_id;e[i]||(e[i]=await s(i));const t=e[i];msg.activity.name||(msg.activity.name=t.name)}t.dispatch({type:"LOCAL_ACTIVITY_UPDATE",...msg})}}),1e4); | ||||
| @@ -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) | ||||
| @@ -2,7 +2,8 @@ | ||||
| <RCC> | ||||
|   <qresource> | ||||
|     <file>assets/userscript.js</file> | ||||
|     <file>assets/bridge_mod.js</file> | ||||
|     <file>assets/arrpc_bridge_mod.js</file> | ||||
|     <file>assets/arrpc.js</file> | ||||
|     <file>assets/de.shorsh.discord-screenaudio.png</file> | ||||
|   </qresource> | ||||
| </RCC> | ||||
							
								
								
									
										36
									
								
								scripts/build_arrpc.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										36
									
								
								scripts/build_arrpc.sh
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,36 @@ | ||||
| #!/usr/bin/bash | ||||
| set -e | ||||
|  | ||||
| cd "$(dirname "$0")/../submodules" | ||||
|  | ||||
| echo_status() { | ||||
|   echo | ||||
|   echo | ||||
|   echo "-> $1..." | ||||
| } | ||||
|  | ||||
| if [ ! -d "arrpc" ]; then | ||||
|   echo_status "Cloning arRPC" | ||||
|   git clone https://github.com/OpenAsar/arrpc.git | ||||
|   cd arrpc | ||||
| else | ||||
|   echo_status "Fetching arRPC changes" | ||||
|   cd arrpc | ||||
|   git fetch | ||||
| fi | ||||
|  | ||||
| echo_status "Checking out latest commit" | ||||
| git reset --hard HEAD | ||||
| git checkout main | ||||
|  | ||||
| echo_status "Installing dependencies" | ||||
| pnpm i -D @vercel/ncc | ||||
|  | ||||
| echo_status "Patching arRPC" | ||||
| sed -i 's/"type": "module",//' package.json | ||||
|  | ||||
| echo_status "Building arRPC" | ||||
| pnpm exec ncc build -m src/index.js | ||||
|  | ||||
| echo_status "Copying built file" | ||||
| cp -v ./dist/index.js ../../assets/arrpc.js | ||||
| @@ -9,6 +9,7 @@ | ||||
| #include <QFileInfo> | ||||
| #include <QMessageBox> | ||||
| #include <QNetworkReply> | ||||
| #include <QTemporaryFile> | ||||
| #include <QTimer> | ||||
| #include <QWebChannel> | ||||
| #include <QWebEngineScript> | ||||
| @@ -35,11 +36,9 @@ DiscordPage::DiscordPage(QWidget *parent) : QWebEnginePage(parent) { | ||||
|  | ||||
|   injectFile(&DiscordPage::injectScript, "userscript.js", | ||||
|              ":/assets/userscript.js"); | ||||
|               | ||||
|   injectFile(&DiscordPage::injectScript, "bridge_mod.js", | ||||
|              ":/assets/bridge_mod.js"); | ||||
|  | ||||
|   setupUserStyles(); | ||||
|   setupArrpc(); | ||||
| } | ||||
|  | ||||
| void DiscordPage::setupPermissions() { | ||||
| @@ -283,10 +282,11 @@ void DiscordPage::javaScriptConsoleMessage( | ||||
|           ansi += "\033[" + cssAnsiColorMap[color] + "m"; | ||||
|       } | ||||
|     } | ||||
|     qDebug(discordLog) << (ansi + lines[0].trimmed() + "\033[0m " + | ||||
|                            lines[endOfStyles].trimmed()) | ||||
|                               .toUtf8() | ||||
|                               .constData(); | ||||
|     if (endOfStyles < lines.length()) | ||||
|       qDebug(discordLog) << (ansi + lines[0].trimmed() + "\033[0m " + | ||||
|                              lines[endOfStyles].trimmed()) | ||||
|                                 .toUtf8() | ||||
|                                 .constData(); | ||||
|     for (auto line : lines.mid(endOfStyles + 1)) { | ||||
|       qDebug(discordLog) << line.toUtf8().constData(); | ||||
|     } | ||||
| @@ -294,3 +294,19 @@ void DiscordPage::javaScriptConsoleMessage( | ||||
| } | ||||
|  | ||||
| UserScript *DiscordPage::userScript() { return &m_userScript; } | ||||
|  | ||||
| void DiscordPage::setupArrpc() { | ||||
|   QFile nodejs("/usr/bin/node"); | ||||
|   if (nodejs.exists()) { | ||||
|     auto arrpcSource = QTemporaryFile::createNativeFile(":/assets/arrpc.js"); | ||||
|     qDebug(mainLog).noquote() | ||||
|         << "NodeJS found, starting arRPC located at" << arrpcSource->fileName(); | ||||
|     m_arrpcProcess.setProcessChannelMode(QProcess::ForwardedChannels); | ||||
|     m_arrpcProcess.setProgram(nodejs.fileName()); | ||||
|     m_arrpcProcess.setArguments(QStringList{arrpcSource->fileName()}); | ||||
|     m_arrpcProcess.start(); | ||||
|  | ||||
|     injectFile(&DiscordPage::injectScript, "arrpc_bridge_mod.js", | ||||
|                ":/assets/arrpc_bridge_mod.js"); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -5,6 +5,7 @@ | ||||
| #include <QDir> | ||||
| #include <QFile> | ||||
| #include <QNetworkAccessManager> | ||||
| #include <QProcess> | ||||
| #include <QStandardPaths> | ||||
| #include <QWebEngineFullScreenRequest> | ||||
| #include <QWebEnginePage> | ||||
| @@ -22,10 +23,12 @@ private: | ||||
|   QFile *m_userStylesFile; | ||||
|   QString m_userStylesContent; | ||||
|   QNetworkAccessManager m_networkAccessManager; | ||||
|   QProcess m_arrpcProcess; | ||||
|   const QDir m_configLocation = | ||||
|       QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation); | ||||
|   void setupPermissions(); | ||||
|   void setupUserStyles(); | ||||
|   void setupArrpc(); | ||||
|   void fetchUserStyles(); | ||||
|   bool acceptNavigationRequest(const QUrl &url, | ||||
|                                QWebEnginePage::NavigationType type, | ||||
|   | ||||
| @@ -30,6 +30,7 @@ MainWindow::MainWindow(bool useNotifySend, QWidget *parent) | ||||
|   m_centralWidget = new CentralWidget(this); | ||||
|   setCentralWidget(m_centralWidget); | ||||
|   setupTrayIcon(); | ||||
|   setMinimumSize(800, 300); | ||||
|   if (m_settings->contains("geometry")) { | ||||
|     restoreGeometry(m_settings->value("geometry").toByteArray()); | ||||
|   } else { | ||||
| @@ -99,7 +100,8 @@ void MainWindow::cleanTrayIcon() { | ||||
| } | ||||
|  | ||||
| void MainWindow::setupSettings() { | ||||
|   m_settings = new QSettings("maltejur", "discord-screenaudio", this); | ||||
|   m_settings = | ||||
|       new QSettings("discord-screenaudio", "discord-screenaudio", this); | ||||
|   m_settings->beginGroup("settings"); | ||||
|   m_settings->endGroup(); | ||||
| } | ||||
|   | ||||
| @@ -44,6 +44,10 @@ void UserScript::setupHelpMenu() { | ||||
|       QString(), "https://github.com/Curve"); | ||||
|   aboutData.addComponent("Rohrkabel", "A C++ RAII Pipewire-API Wrapper", "1.3", | ||||
|                          "https://github.com/Soundux/rohrkabel"); | ||||
|   aboutData.addComponent("arRPC", | ||||
|                          "An open implementation of Discord's local RPC " | ||||
|                          "servers<br>Copyright (c) 2022 OpenAsar", | ||||
|                          nullptr, "https://github.com/OpenAsar/arrpc"); | ||||
|   m_helpMenu = new KHelpMenu(MainWindow::instance(), aboutData); | ||||
| #endif | ||||
| } | ||||
|   | ||||
| @@ -8,6 +8,25 @@ namespace Virtmic { | ||||
|  | ||||
| const QStringList EXCLUDE_TARGETS{"Chromium input", "discord-screenaudio"}; | ||||
|  | ||||
| const std::string nullstr = ""; | ||||
| const std::string &getTarget(const pipewire::spa::dict &props) { | ||||
|   if (props.count("media.class") && | ||||
|       props.at("media.class") == "Stream/Output/Audio") { | ||||
|     if (props.count("application.name") && props.at("application.name") != "") | ||||
|       return props.at("application.name"); | ||||
|     else if (props.count("application.process.binary") && | ||||
|              props.at("application.process.binary") != "") | ||||
|       return props.at("application.process.binary"); | ||||
|     else | ||||
|       return props.at("node.name"); | ||||
|   } else | ||||
|     return nullstr; | ||||
| } | ||||
|  | ||||
| QString qGetTarget(const pipewire::spa::dict &props) { | ||||
|   return QString::fromStdString(getTarget(props)); | ||||
| } | ||||
|  | ||||
| QVector<QString> getTargets() { | ||||
|   auto main_loop = pipewire::main_loop(); | ||||
|   auto context = pipewire::context(main_loop); | ||||
| @@ -22,14 +41,7 @@ QVector<QString> getTargets() { | ||||
|         if (global.type == pipewire::node::type) { | ||||
|           auto node = reg.bind<pipewire::node>(global.id); | ||||
|           auto info = node.info(); | ||||
|           QString name; | ||||
|           if (info.props.count("application.name") && | ||||
|               info.props["application.name"] != "") | ||||
|             name = QString::fromStdString(info.props["application.name"]); | ||||
|           else | ||||
|             name = QString::fromStdString( | ||||
|                 info.props["application.process.binary"]); | ||||
|  | ||||
|           QString name = qGetTarget(info.props); | ||||
|           if (name != "" && !EXCLUDE_TARGETS.contains(name) && | ||||
|               !targets.contains(name)) { | ||||
|             targets.append(name); | ||||
| @@ -73,13 +85,7 @@ void start(QString _target) { | ||||
|         continue; | ||||
|  | ||||
|       auto &parent = nodes.at(parent_id); | ||||
|       std::string name; | ||||
|       if (parent.props.count("application.name") && | ||||
|           parent.props["application.name"] != "") | ||||
|         name = parent.props["application.name"]; | ||||
|       else | ||||
|         name = parent.props["application.process.binary"]; | ||||
|  | ||||
|       std::string name = getTarget(parent.props); | ||||
|       if (name == target || | ||||
|           (target == "[All Desktop Audio]" && | ||||
|            !EXCLUDE_TARGETS.contains(QString::fromStdString(name)))) { | ||||
| @@ -99,7 +105,7 @@ void start(QString _target) { | ||||
|     } | ||||
|   }; | ||||
|  | ||||
|   std::string target = _target.toLatin1().toStdString(); | ||||
|   std::string target = _target.toUtf8().toStdString(); | ||||
|  | ||||
|   auto virtual_mic = core.create("adapter", | ||||
|                                  {{"node.name", "discord-screenaudio-virtmic"}, | ||||
| @@ -123,13 +129,8 @@ void start(QString _target) { | ||||
|         if (global.type == pipewire::node::type) { | ||||
|           auto node = reg.bind<pipewire::node>(global.id); | ||||
|           auto info = node.info(); | ||||
|           std::string name; | ||||
|           if (info.props.count("application.name") && | ||||
|               info.props["application.name"] != "") | ||||
|             name = info.props["application.name"]; | ||||
|           else if (info.props.count("application.process.binary")) { | ||||
|             name = info.props["application.process.binary"]; | ||||
|           } else | ||||
|           std::string name = getTarget(info.props); | ||||
|           if (name == nullstr) | ||||
|             return; | ||||
|           qDebug(virtmicLog) << QString("Added: %1") | ||||
|                                     .arg(QString::fromStdString(name)) | ||||
| @@ -168,12 +169,9 @@ void start(QString _target) { | ||||
|       [&](const std::uint32_t id) { | ||||
|         if (nodes.count(id)) { | ||||
|           auto info = nodes.at(id); | ||||
|           std::string name; | ||||
|           if (info.props.count("application.name") && | ||||
|               info.props["application.name"] != "") | ||||
|             name = info.props["application.name"]; | ||||
|           else | ||||
|             name = info.props["application.process.binary"]; | ||||
|           std::string name = getTarget(info.props); | ||||
|           if (name == nullstr) | ||||
|             return; | ||||
|           qDebug(virtmicLog) << QString("Removed: %1") | ||||
|                                     .arg(QString::fromStdString(name)) | ||||
|                                     .toUtf8() | ||||
|   | ||||
		Reference in New Issue
	
	Block a user